def _commit( self, category: str, type_: str, log_dt: datetime, identifier: str = None, force: bool = False, ) -> None: if category not in [wc.TOKEN_SESSION, wc.TOKEN_TASK]: raise ValueError( f'Category must be one of {", ".join([wc.TOKEN_SESSION, wc.TOKEN_TASK])}' ) if type_ not in [wc.TOKEN_START, wc.TOKEN_STOP]: raise ValueError( f'Type must be one of {", ".join([wc.TOKEN_START, wc.TOKEN_STOP])}' ) commit_dt = now_localtz() # Test if there are running tasks if category == wc.TOKEN_SESSION: date_mask = self._log_df["date"] == log_dt.date() task_mask = self._log_df[wc.COL_CATEGORY] == wc.TOKEN_TASK mask = date_mask & task_mask active_tasks = get_active_task_ids(self._log_df[mask]) if len(active_tasks) > 0: if not force: msg = ErrMsg.STOP_SESSION_TASKS_RUNNING.value.format( active_tasks=active_tasks) sys.stderr.write(msg + "\n") sys.exit(1) else: for task_id in active_tasks: self._commit(wc.TOKEN_TASK, wc.TOKEN_STOP, log_dt, task_id) cols = [col for col, _ in self._schema] values = [ pd.to_datetime(commit_dt), pd.to_datetime(log_dt), category, type_, identifier, ] record = pd.DataFrame( dict(zip(cols, values)), index=[0], ) record_t = pd.concat([record, extract_date_and_time(record)], axis=1) # append record to in-memory log self._log_df = pd.concat((self._log_df, record_t)) # and persist to disk self._persist(record_t, mode="a") # Because we allow for time offsets sorting is not guaranteed at this point. # Update sorting of values in-memory. self._log_df = self._log_df.sort_values(by=[wc.COL_LOG_DATETIME])
def stop_active_tasks(self, log_dt: datetime): """Stop all active tasks by commiting changes to the logfile.""" query_date = log_dt.date() task_mask = self._log_df[wc.COL_CATEGORY] == wc.TOKEN_TASK date_mask = self._log_df["date"] == query_date mask = task_mask & date_mask active_task_ids = get_active_task_ids(self._log_df[mask]) for task_id in active_task_ids: self._commit(wc.TOKEN_TASK, wc.TOKEN_STOP, log_dt, identifier=task_id)
def status( self, hours_target: float, hours_max: float, query_date: date, fmt: str = None, ) -> None: """Display the current working status, e.g. total time worked at this day, remaining time, etc.""" self._check_nonempty_or_exit(fmt) df_day = self._filter_date_category_limit_cols(query_date) if df_day.shape[0] == 0: if fmt is None: msg = ErrMsg.EMPTY_LOG_DATA_FOR_DATE.value.format( query_date=query_date) sys.stderr.write(msg + "\n") sys.exit(1) else: sys.stdout.write(ErrMsg.NA.value) sys.exit(0) is_active = is_active_session(df_day) self.logger.debug(f"Is active: {is_active}") df_day = self._add_sentinel(query_date, df_day) facts = self._calc_facts(df_day, hours_target, hours_max) date_mask = self._log_df["date"] == query_date task_mask = self._log_df[wc.COL_CATEGORY] == wc.TOKEN_TASK sel_task_mask = date_mask & task_mask touched_tasks = get_all_task_ids_with_duration( self._log_df[sel_task_mask]) active_tasks = get_active_task_ids(self._log_df[sel_task_mask]) lines = [ ("Status", "Tracking {tracking_status}"), ("Total time", "{total_time} ({percentage_done:3}%)"), ("Remaining time", "{remaining_time} ({percentage_remaining:3}%)"), ("Overtime", "{overtime} ({percentage_overtime:3}%)"), ("Break Duration", "{break_duration}"), ( "Touched tasks", "{touched_tasks_stats}", ), ( "Active tasks", "{active_tasks_stats}", ), ] if is_active and date == "today": lines += [( "End of work", "{eow}", )] key_max_len = max([len(line[0]) for line in lines]) fmt_string = "{:" + str(key_max_len + 1) + "s}: {}" stdout_fmt = "\n".join(fmt_string.format(*line) for line in lines) + "\n" sys.stdout.write((stdout_fmt if fmt is None else fmt).format( **facts, active_tasks=", ".join(active_tasks), active_tasks_stats=f"({len(active_tasks)}) [" + ", ".join(active_tasks) + "]", touched_tasks=", ".join(touched_tasks.keys()), touched_tasks_stats=f"({len(touched_tasks)}) [" + ", ".join([ f"{k} ({format_timedelta(v)})" for k, v in touched_tasks.items() ]) + "]", tracking_status="on" if is_active else "off", ))
def test_get_active_task_ids_multiple(self): df = read_log_sample("tasks_multiple_started") expected = ["task1", "task3"] actual = get_active_task_ids(df) self.assertEqual(actual, expected)
def test_get_active_task_ids_single(self): df = read_log_sample("tasks_simple_active") expected = ["task1"] actual = get_active_task_ids(df) self.assertEqual(actual, expected)
def test_get_active_task_ids_empty(self): df = empty_df_from_schema(SCHEMA) expected = [] actual = get_active_task_ids(df) self.assertEqual(actual, expected)