예제 #1
0
    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])
예제 #2
0
 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)
예제 #3
0
    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",
        ))
예제 #4
0
 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)
예제 #5
0
 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)
예제 #6
0
 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)