Пример #1
0
def exec_added(entity, config):
    # type: (Entity, Config) -> bool
    times = re.compile('\d{1,2}:\d{2}').findall(entity.content)
    if times:
        hour, minute = times[0].split(':')
        target_time = O(entity.due_date_utc) \
            .then(lambda d: to_datetime(d, config.timezone)) \
            .or_(datetime.now(timezone(config.timezone)))
        begin_time = minus3h(target_time).replace(hour=int(hour),
                                                  minute=int(minute),
                                                  second=0)
        r = api.add_reminder(
            config.todoist.api_token, entity.id,
            begin_time - timedelta(minutes=config.remind.minutes_delta))
        if not r:
            return False

    api.notify_slack(
        config.message_format_by_event[entity.event].format(
            **entity.to_dict()), config)
    return True
Пример #2
0
def exec_completed(entity, config):
    # type: (Entity, Config) -> bool

    # Ignore the task which is child task and closed in past
    if entity.in_history and entity.parent_id:
        return True

    special_event = config.special_events.find_by_id(entity.id)  # type: Event
    if special_event:
        # TODO: Create independent function
        api.notify_slack(py_.sample(special_event.messages), config)
        if special_event == config.special_events.leave_work:
            scheduled, interrupted = create_daily_report(
                config)  # type: TList[TaskReport]
            report_message = """
:spiral_calendar_pad: *朝に予定したタスク* `{total_scheduled}時間 (予定: {scheduled_estimate}時間)`

{scheduled}


:ikiro: *割り込みで入ったタスク* `{total_interrupted}時間`

{interrupted}
            """.format(
                total_scheduled=round(scheduled.sum_by(_.elapsed) / 60.0, 1),
                scheduled_estimate=round(
                    scheduled.filter(_.estimate).sum_by(_.estimate) / 60.0, 1),
                total_interrupted=round(
                    interrupted.sum_by(_.elapsed) / 60.0, 1),
                scheduled=scheduled.map(lambda r: to_report_string(
                    r, config.daily_report_format)).join("\n"),
                interrupted=interrupted.map(lambda r: to_report_string(
                    r, config.daily_report_format)).join("\n"),
            )
            api.notify_slack(report_message, config)
        elif special_event == config.special_events.start_work:
            scheduled_tasks = api.fetch_uncompleted_tasks(config.todoist.api_token) \
                .filter(lambda x: equal_now_day(x.due_date_utc, config.timezone)) \
                .filter(lambda x:
                        is_valid_project(config, x.project_id) or
                        in_special_events(config, x.id)
                        ) \
                .order_by(_.day_order) \
                .order_by(_.priority, reverse=True)
            """:type: TList[TodoistApiTask]"""

            S3.put_object(
                Bucket=BUCKET,
                Key=SCHEDULED_TASKS,
                Body=scheduled_tasks.reject(
                    lambda x: in_special_events(config, x.id)).to_json())

            api.notify_slack(
                scheduled_tasks.map(lambda x:
                                    config.morning_report_format.base.format(
                                        name=x.content,
                                        project_name=O(config.project_by_id.get(x.project_id)).then(_.name).or_("節目"),
                                        estimate_label=O(
                                            config.special_labels.estimates.find(lambda l: l.id in x.labels)
                                        ).then(_.name).or_('----')
                                    )) \
                .join("\n"),
                config
            )
        elif special_event == config.special_events.start_make_schedule:
            earliers, laters = api.fetch_uncompleted_tasks(config.todoist.api_token) \
                .filter(lambda x: equal_now_day(x.due_date_utc, config.timezone)) \
                .reject(lambda x: x.id in config.daily_default_order) \
                .partial(lambda x: O(config.project_by_id.get(x.project_id)).then_or_none(_.order_earlier))
            """:type: TList[TodoistApiTask]"""

            api.update_day_orders(
                config.todoist.api_token,
                earliers.map(_.id) + config.daily_default_order +
                laters.map(_.id))
    else:
        next_item = config.next_message_format and fetch_next_item(config)
        next_message = config.next_message_format.format(
            **next_item) if next_item is not None else ''
        message = config.message_format_by_event[entity.event].format(
            **entity.to_dict()) + '\n' + next_message
        api.notify_slack(message, config)

    # Toggl action
    current_entry = api.access_toggl('/time_entries/current',
                                     config.toggl.api_token).json()['data']
    if not current_entry or current_entry[
            'description'] != entity.content_without_emoji:
        return True

    api.access_toggl('/time_entries/{}/stop'.format(current_entry['id']),
                     config.toggl.api_token)
    return True
Пример #3
0
 def test_then_not_none(self):
     assert O(1).then(lambda x: x + 1).or_(-1) == 2
Пример #4
0
def create_daily_report(config):
    # type: (Config) -> Tuple[TList[TaskReport], TList[TaskReport]]

    # todoist
    completed_todoist_reports = api.fetch_completed_tasks(
        config.todoist.api_token,
        minus3h(now(config.timezone)).replace(hour=0, minute=0, second=0)
    ).map(lambda x: TaskReport.from_dict({
        "id":
        x["task_id"],
        "name":
        x["content"].split(" @")[0],
        "project_id":
        x["project_id"],
        "project_name":
        O(config.project_by_id.get(x["project_id"])).then(_.name).or_("なし"),
        "is_waiting":
        config.special_labels.waiting.name in x["content"].split(" @")[1:],
        "completed_date":
        x["completed_date"],
        "estimate":
        O(
            config.special_labels.estimates.find(lambda l: l.name in x[
                "content"].split(" @")[1:])).then_or_none(_.value)
    })).filter(lambda x: is_measured_project(config, x.project_id))
    """:type: TList[TaskReport]"""

    uncompleted_todoist_reports = api.fetch_uncompleted_tasks(config.todoist.api_token) \
        .map(lambda x: TaskReport.from_dict({
            "id": x.id,
            "name": x.content,
            "project_id": x.project_id,
            "project_name": O(config.project_by_id.get(x.project_id)).then(_.name).or_("なし"),
            "is_waiting": config.special_labels.waiting.id in x.labels,
            "due_date_utc": x.due_date_utc,
            "estimate": O(
                config.special_labels.estimates.find(lambda l: l.id in x.labels)
            ).then_or_none(_.value)
        })).filter(lambda x: is_measured_project(config, x.project_id))
    """:type: TList[TaskReport]"""

    # toggl
    toggl_reports = api.fetch_reports(workspace_id=config.toggl.workspace,
                                     since=minus3h(now(config.timezone)),
                                     api_token=config.toggl.api_token) \
        .group_by(_.task_uniq_key) \
        .to_values() \
        .map(lambda xs: toggl_to_task_report(config, xs, completed_todoist_reports + uncompleted_todoist_reports))
    """:type: TList[TaskReport]"""

    work_start_date = completed_todoist_reports \
        .find(_.id == config.special_events.start_work.id) \
        .completed_date
    """:type: datetime"""

    # ---
    s3_obj = S3.get_object(Bucket=BUCKET, Key=SCHEDULED_TASKS)
    # The task completed before scheduling is the scheduled
    done_before_scheduled = toggl_reports \
        .filter(lambda x: completed_todoist_reports.find(_.id == x.id)) \
        .filter(lambda x: completed_todoist_reports.find(_.id == x.id).completed_date <= work_start_date)
    """:type: TList[TaskReport]"""

    scheduled_reports = TodoistApiTask.from_json_to_list(s3_obj['Body'].read()) \
        .map(lambda x: TaskReport.from_dict({
            "id": x.id,
            "name": x.content,
            "project_id": x.project_id,
            "project_name": O(config.project_by_id.get(x.project_id)).then(_.name).or_("なし"),
            "is_waiting": config.special_labels.waiting.id in x.labels,
            "elapsed": O(toggl_reports.find(_.id == x.id)).then(_.elapsed).or_(0),
            "due_date_utc": x.due_date_utc,
            "estimate": O(
                config.special_labels.estimates.find(lambda l: l.id in x.labels)
            ).then_or_none(_.value),
            "status": Status.COMPLETED if x.id in completed_todoist_reports.map(_.id) \
                else Status.REMOVED if not x.id in uncompleted_todoist_reports.map(_.id) \
                else Status.RE_SCHEDULED if to_datetime(x.due_date_utc, config.timezone).day != minus3h(now(config.timezone)).day \
                else Status.WAITING if config.special_labels.waiting.id in x.labels \
                else Status.NOT_STARTED_YET if O(toggl_reports.find(_.id == x.id)).then(_.elapsed).or_(0) == 0 \
                else Status.IN_PROGRESS
            })) \
        .filter(lambda x: is_measured_project(config, x.project_id))
    """:type: TList[TaskReport]"""

    measured_interrupted_reports = toggl_reports \
        .reject(lambda x: x.id in (scheduled_reports + done_before_scheduled).map(_.id))
    """:type: TList[TaskReport]"""
    unmeasured_interrupted_reports = uncompleted_todoist_reports \
        .filter(lambda x: equal_now_day(x.due_date_utc, config.timezone)) \
        .reject(lambda x: x.id in measured_interrupted_reports.map(_.id)) \
        .reject(lambda x: x.id in (scheduled_reports + done_before_scheduled).map(_.id))
    """:type: TList[TaskReport]"""

    interrupted_reports = (measured_interrupted_reports + unmeasured_interrupted_reports) \
        .map(lambda x: TaskReport.from_dict({
            "id": x.id,
            "name": x.name,
            "project_id": x.project_id,
            "project_name": x.project_name,
            "is_waiting": x.is_waiting,
            "elapsed": x.elapsed,
            "completed_date": x.completed_date,
            "due_date_utc": x.due_date_utc,
            "estimate": x.estimate,
            "status": Status.COMPLETED if x.id in completed_todoist_reports.map(_.id) \
                else Status.COMPLETED if not x.id in uncompleted_todoist_reports.map(_.id) \
                else Status.WAITING if x.is_waiting \
                else Status.NOT_STARTED_YET if x.elapsed == 0 \
                else Status.IN_PROGRESS
        }))

    return scheduled_reports, interrupted_reports
Пример #5
0
 def test_then_or_none_empty_list(self):
     assert O([]).then_or_none(lambda x: x) == []
Пример #6
0
 def test_then_or_none_empty_dict(self):
     assert O({}).then_or_none(lambda x: x) == {}
Пример #7
0
 def test_then_or_none_zero(self):
     assert O(0).then_or_none(lambda x: x) == 0
Пример #8
0
 def test_then_or_none_none(self):
     assert O(None).then_or_none(lambda x: x + 1) is None
Пример #9
0
 def test_then_or_none_not_none(self):
     assert O(1).then_or_none(lambda x: x + 1) == 2
Пример #10
0
 def test_then_empty_dict(self):
     assert O({}).then(lambda x: x).or_("hoge") == {}
Пример #11
0
 def test_then_empty_list(self):
     assert O([]).then(lambda x: x).or_("hoge") == []
Пример #12
0
 def test_then_none(self):
     assert O(None).then(lambda x: x + 1).or_("hoge") == "hoge"
Пример #13
0
 def test_then_zero(self):
     assert O(0).then(lambda x: x).or_(-1) == 0