diff --git a/Clockify/Clockify_Send_Daily_Activity_Brief_to_Slack.ipynb b/Clockify/Clockify_Send_Daily_Activity_Brief_to_Slack.ipynb deleted file mode 100644 index 64ca4d4014..0000000000 --- a/Clockify/Clockify_Send_Daily_Activity_Brief_to_Slack.ipynb +++ /dev/null @@ -1,274 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "389e6be9-1a6e-4fcb-b091-e31607ec8da6", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "\"Naas\"" - ] - }, - { - "cell_type": "markdown", - "id": "36722081-8746-486c-a539-6a65f45732bb", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "# Clockify - Send Daily Activity Brief to Slack" - ] - }, - { - "cell_type": "markdown", - "id": "933b1463-8fc8-40a3-ba4f-19db5253599b", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "**Tags:** #clockify #slack #activity #brief #daily #automation" - ] - }, - { - "cell_type": "markdown", - "id": "e0beb301-9271-41a4-b16a-ce2fee7db1c9", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "**Author:** [Florent Ravenel](https://www.linkedin.com/in/florent-ravenel/)" - ] - }, - { - "cell_type": "markdown", - "id": "61596618-5ddf-4f83-8b0a-239e72060930", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "**Last update:** 2023-10-11 (Created: 2023-10-11)" - ] - }, - { - "cell_type": "markdown", - "id": "80cd984b-9345-452c-ad44-72dcdc8b26e1", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "**Description:** This notebook automates the process of sending a daily activity brief from Clockify to Slack. It is usefull for organizations to keep track of their team's daily activities." - ] - }, - { - "cell_type": "markdown", - "id": "951332ed-d0a6-4930-b9ff-69a66f3bbcfd", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "**References:**\n- [Clockify API Documentation](https://clockify.github.io/clockify_api_docs/)\n- [Slack API Documentation](https://api.slack.com/docs)" - ] - }, - { - "cell_type": "markdown", - "id": "855b70c1-9f73-4cba-bd43-6a8773e494f1", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "## Input" - ] - }, - { - "cell_type": "markdown", - "id": "ce51e72d-4b4a-4a33-8619-cc02cdcf238b", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "### Import libraries" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c821e4c-54c3-40a5-b1fe-c8f3d986f9c5", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": "import requests\nimport json", - "outputs": [] - }, - { - "cell_type": "markdown", - "id": "1a6aa89f-f3c1-4d96-8ecc-fa7921ea3ee8", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "### Setup variables\n- `clockify_api_key`: Clockify API key. [Get your API key here](https://clockify.me/user/settings).\n- `slack_token`: Slack token. [Get your token here](https://api.slack.com/custom-integrations/legacy-tokens).\n- `slack_channel`: Slack channel to post the daily activity brief." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "48c0a7d3-cbe8-4508-849c-9aee7bbf9960", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": "clockify_api_key = \"\"\nslack_token = \"\"\nslack_channel = \"\"", - "outputs": [] - }, - { - "cell_type": "markdown", - "id": "851b98ee-d339-4d4d-8eab-1e008e8a2590", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "## Model" - ] - }, - { - "cell_type": "markdown", - "id": "beb8d3b5-8be3-4a74-9cab-f9466dd0dc8d", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "### Get daily activity brief" - ] - }, - { - "cell_type": "markdown", - "id": "f6fe61c4-4262-4405-a40d-6646bf1362e4", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "Retrieve the daily activity brief from Clockify API and store it in a variable." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f084002a-cd76-4ba9-9c19-b0a527320835", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": "# Set the request headers\nheaders = {\"Content-Type\": \"application/json\", \"X-Api-Key\": clockify_api_key}\n# Set the request parameters\nparams = {\n \"workspace\": \"\",\n \"user\": \"\",\n \"start\": \"\",\n \"end\": \"\",\n}\n# Make the request\nresponse = requests.get(\n \"https://api.clockify.me/api/v1/reports/summary\", headers=headers, params=params\n)\n# Store the response in a variable\ndaily_activity_brief = response.json()", - "outputs": [] - }, - { - "cell_type": "markdown", - "id": "00f4e125-3d1b-450c-9836-b72022b7c9f6", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "### Post daily activity brief to Slack" - ] - }, - { - "cell_type": "markdown", - "id": "4bd2bad1-237f-4a9b-95a9-650a1d141565", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "Post the daily activity brief to Slack." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2eb3b92c-3ae5-4806-9239-092b72b053ed", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": "# Set the request headers\nheaders = {\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"Authorization\": \"Bearer \" + slack_token,\n}\n# Set the request body\nbody = {\n \"channel\": slack_channel,\n \"text\": \"Daily activity brief:\",\n \"attachments\": [{\"text\": json.dumps(daily_activity_brief, indent=4)}],\n}\n# Make the request\nresponse = requests.post(\n \"https://slack.com/api/chat.postMessage\", headers=headers, data=json.dumps(body)\n)", - "outputs": [] - }, - { - "cell_type": "markdown", - "id": "d943244f-dc15-44ca-98a9-c00df3976124", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "## Output" - ] - }, - { - "cell_type": "markdown", - "id": "327dca23-43d6-40e4-a7f1-fda12179c445", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": [ - "### Display result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3a87fb89-698f-41c0-b2eb-c8d622bc965e", - "metadata": { - "papermill": {}, - "tags": [] - }, - "source": "# Print the response\nprint(response.text)", - "outputs": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.6" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": {}, - "version_major": 2, - "version_minor": 0 - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/Clockify/Clockify_Send_daily_activity_brief_to_Slack.ipynb b/Clockify/Clockify_Send_daily_activity_brief_to_Slack.ipynb new file mode 100644 index 0000000000..6630344265 --- /dev/null +++ b/Clockify/Clockify_Send_daily_activity_brief_to_Slack.ipynb @@ -0,0 +1,585 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "389e6be9-1a6e-4fcb-b091-e31607ec8da6", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "\"Naas\"" + ] + }, + { + "cell_type": "markdown", + "id": "36722081-8746-486c-a539-6a65f45732bb", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "# Clockify - Send daily activity brief to Slack" + ] + }, + { + "cell_type": "markdown", + "id": "933b1463-8fc8-40a3-ba4f-19db5253599b", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "**Tags:** #clockify #slack #activity #brief #daily #automation" + ] + }, + { + "cell_type": "markdown", + "id": "e0beb301-9271-41a4-b16a-ce2fee7db1c9", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "**Author:** [Florent Ravenel](https://www.linkedin.com/in/florent-ravenel/)" + ] + }, + { + "cell_type": "markdown", + "id": "61596618-5ddf-4f83-8b0a-239e72060930", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "**Last update:** 2023-10-11 (Created: 2023-10-11)" + ] + }, + { + "cell_type": "markdown", + "id": "80cd984b-9345-452c-ad44-72dcdc8b26e1", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "**Description:** This notebook automates the process of sending a daily activity brief from Clockify to Slack. It is usefull for organizations to keep track of their team's daily activities." + ] + }, + { + "cell_type": "markdown", + "id": "951332ed-d0a6-4930-b9ff-69a66f3bbcfd", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "**References:**\n", + "- [Clockify API Documentation](https://clockify.github.io/clockify_api_docs/)\n", + "- [Slack API Documentation](https://api.slack.com/docs)" + ] + }, + { + "cell_type": "markdown", + "id": "00b0e091-c767-46dd-a237-6fc6870db301", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "## Input" + ] + }, + { + "cell_type": "markdown", + "id": "e438858a-f719-4698-93c8-c25bf52964a4", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Import libraries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12b0fc1f-a48f-46dc-80c1-a8f93858ede9", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "import requests\n", + "import naas\n", + "import pandas as pd\n", + "from datetime import datetime, date, timedelta, time\n", + "from naas_drivers import slack" + ] + }, + { + "cell_type": "markdown", + "id": "7439537e-b364-4c06-a551-1ba2207c0cdc", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Setup variables\n", + "**Mandatory**\n", + "- `api_key`: [Get your API key](https://clockify.me/user/settings)\n", + "- `workspace_id`: ID of the workspace\n", + "- `user_id`: ID of the user to get time entries from\n", + "- `slack_token`: User Slack token\n", + "- `slack_channel`: the channel where you wish to send the message\n", + "\n", + "**Optional**\n", + "- `cron`: This variable represents the CRON syntax used to run the scheduler. More information here: https://crontab.guru/#0_12,18_*_*_1-5\n", + "- `start_date`: Start date of the timeframe to create the time entries database\n", + "- `end_date`: End date of the timeframe to create the time entries database" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "736cfc94-75e5-4efa-b17e-c164df51dd30", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "# Mandatory\n", + "api_key = naas.secret.get(\"CLOCKIFY_API_KEY\") or \"YOUR_API_KEY\"\n", + "workspace_id = \"626f9e3b36c2670314c0386e\" #\"\"\n", + "user_id = \"626fa819f87fd71e0e1f392c\"\n", + "slack_token = naas.secret.get(\"SLACK_USER_TOKEN\")\n", + "slack_channel = \"core-team-chat\"\n", + "\n", + "# Optional\n", + "cron = \"0 * * * 2-5\"\n", + "start_date = (today - timedelta(days=1)).strftime(\"%Y-%m-%d\")\n", + "end_date = (datetime.now().replace(hour=23, minute=59, second=59, microsecond=0).astimezone() - timedelta(days=1)).isoformat()" + ] + }, + { + "cell_type": "markdown", + "id": "d746baad-e9ae-4c52-8899-b3ce918f55cc", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "## Model" + ] + }, + { + "cell_type": "markdown", + "id": "788921c8-02d4-48a6-b22f-13ce210b6988", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Function: Flatten the nested dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "539b5ca1-b3ee-4fd4-bb30-6b55965eac3b", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "# Flatten the nested dict\n", + "def flatten_dict(d, parent_key='', sep='_'):\n", + " \"\"\"\n", + " Flattens a nested dictionary into a single level dictionary.\n", + "\n", + " Args:\n", + " d (dict): A nested dictionary.\n", + " parent_key (str): Optional string to prefix the keys with.\n", + " sep (str): Optional separator to use between parent_key and child_key.\n", + "\n", + " Returns:\n", + " dict: A flattened dictionary.\n", + " \"\"\"\n", + " items = []\n", + " for k, v in d.items():\n", + " new_key = f\"{parent_key}{sep}{k}\" if parent_key else k\n", + " if isinstance(v, dict):\n", + " items.extend(flatten_dict(v, new_key, sep=sep).items())\n", + " else:\n", + " items.append((new_key, v))\n", + " return dict(items)" + ] + }, + { + "cell_type": "markdown", + "id": "fea09d38-f442-4255-ad62-fefd51f9f443", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Function: Get referentials from workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c61ded79-99f1-4826-b3ab-81f1ea474c87", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "def get_data(api_key, workspace_id, endpoint):\n", + " # Init\n", + " page = 1\n", + " df = pd.DataFrame()\n", + " \n", + " while True:\n", + " # Requests\n", + " url = f\"https://api.clockify.me/api/v1/workspaces/{workspace_id}/{endpoint}\"\n", + " headers = {\n", + " \"X-Api-Key\": api_key\n", + " }\n", + " params = {\n", + " \"page\": page,\n", + " \"page-size\": 100\n", + " }\n", + " res = requests.get(url, headers=headers, params=params)\n", + " data = res.json()\n", + " if len(data) > 0:\n", + " for d in data:\n", + " res = flatten_dict(d)\n", + " tmp_df = pd.DataFrame([res])\n", + " df = pd.concat([df, tmp_df])\n", + " else:\n", + " break\n", + " page += 1\n", + " return df.reset_index(drop=True)" + ] + }, + { + "cell_type": "markdown", + "id": "440de640-7a97-466a-b305-c960960ed815", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Get time entries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e03c86de-820a-4bfa-b7ec-9ef24543330f", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "def get_time_entries(\n", + " api_key,\n", + " workspace_id,\n", + " user_id,\n", + " start_date,\n", + " end_date\n", + "):\n", + " # Init\n", + " start_date = datetime.strptime(start_date, \"%Y-%m-%d\").astimezone().isoformat() # Format date\n", + " page = 1\n", + " df = pd.DataFrame()\n", + " \n", + " # Get raw data\n", + " while True:\n", + " url = f\"https://api.clockify.me/api/v1/workspaces/{workspace_id}/user/{user_id}/time-entries\"\n", + " headers = {\"X-Api-Key\": api_key}\n", + " params = {\n", + " \"start\": start_date,\n", + " \"end\": end_date,\n", + " \"page\": page,\n", + " \"page-size\": 100\n", + " }\n", + " res = requests.get(url, headers=headers, params=params)\n", + " data = res.json()\n", + " if len(data) > 0:\n", + " for d in data:\n", + " res = flatten_dict(d)\n", + " tmp_df = pd.DataFrame([res])\n", + " df = pd.concat([df, tmp_df]).reset_index(drop=True)\n", + " else:\n", + " break\n", + " page += 1\n", + " return df.reset_index(drop=True)\n", + "\n", + "# Get entries\n", + "database = get_time_entries(api_key, workspace_id, user_id, start_date, end_date)\n", + "print(\"Time entries fetched:\", len(database))\n", + "database#.head(3)" + ] + }, + { + "cell_type": "markdown", + "id": "fc522760-7f46-40a7-8973-d66996bc513f", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Get all projects" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f8225c9-f72e-4cf0-92ed-944a2230c26d", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "df_projects = get_data(api_key, workspace_id, \"projects\")\n", + "df_projects = df_projects.rename(columns={\"id\": \"projectId\", \"name\": \"projectName\"})\n", + "df_projects = df_projects[[\"projectId\", \"projectName\", \"clientId\"]]\n", + "print(\"Projects fetched:\", len(df_projects))\n", + "df_projects.head(1)" + ] + }, + { + "cell_type": "markdown", + "id": "5a387182-8635-420f-85f3-42854c2095a9", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Get all clients" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09d12b5e-074c-43bd-acb3-26cffdedfba8", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "df_clients = get_data(api_key, workspace_id, \"clients\")\n", + "df_clients = df_clients.rename(columns={\"id\": \"clientId\", \"name\": \"clientName\"})\n", + "df_clients = df_clients[[\"clientId\", \"clientName\"]]\n", + "print(\"Clients fetched:\", len(df_clients))\n", + "df_clients.head(1)" + ] + }, + { + "cell_type": "markdown", + "id": "9f56677e-8176-4075-97cf-2f6e79fcac1e", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Create database\n", + "Enrich data with referentials from workspace" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b471447-25bc-435f-9054-e199e6366e2f", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "def create_database(\n", + " df_init,\n", + " df_projects,\n", + " df_clients,\n", + " filters=None\n", + "):\n", + " # Init\n", + " df = df_init.copy()\n", + " \n", + " # Final DB\n", + " df = pd.merge(df, df_projects, how=\"left\", on=\"projectId\")\n", + " df = pd.merge(df, df_clients, how=\"left\", on=\"clientId\")\n", + " df[\"timeduration_Hours\"] = round((pd.to_datetime(df[\"timeInterval_end\"]) - pd.to_datetime(df[\"timeInterval_start\"])).dt.total_seconds() / 3600, 2)\n", + "\n", + " # Select column\n", + " to_group = [\n", + " \"clientName\",\n", + " \"projectName\",\n", + " \"description\",\n", + " ]\n", + " to_agg = {\n", + " \"timeduration_Hours\": \"sum\" \n", + " }\n", + " df = df.groupby(to_group, as_index=False).agg(to_agg)\n", + " return df\n", + " \n", + "df_slack = create_database(\n", + " database,\n", + " df_projects,\n", + " df_clients,\n", + " filters\n", + ")\n", + "print(df_slack.timeduration_Hours.sum())\n", + "df_slack" + ] + }, + { + "cell_type": "markdown", + "id": "70af6cc5-2dcf-4473-ad45-abdd4463dbf2", + "metadata": {}, + "source": [ + "### Create Slack blocks" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "474fd1df-43f1-44ce-8a4c-f36f71ee672f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "blocks = [\n", + " {\n", + " \"type\": \"section\",\n", + " \"text\": {\n", + " \"type\": \"mrkdwn\",\n", + " \"text\": f\"*Here is my brief of yesterday activity: {start_date}*\",\n", + " }\n", + " }\n", + "]\n", + "\n", + "clients = df_slack.clientName.unique()\n", + "for client in clients:\n", + " # Filter on clients\n", + " tmp_df = df_slack[df_slack[\"clientName\"] == client].reset_index(drop=True)\n", + " total = round(tmp_df.timeduration_Hours.sum())\n", + " \n", + " # Get clients\n", + " blocks.append({\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": f\"*{client} : {total} hours*\"}})\n", + " \n", + " # Get project details\n", + " bullet_points = []\n", + " for row in tmp_df.itertuples():\n", + " project_name = row.projectName\n", + " description = row.description\n", + " hours = row.timeduration_Hours\n", + " bullet_points.append(f\"• {project_name}: {description} ({hours} hours)\")\n", + " \n", + " for b in bullet_points:\n", + " blocks.append({\"type\": \"section\", \"text\": {\"type\": \"mrkdwn\", \"text\": b}})\n", + " \n", + "blocks" + ] + }, + { + "cell_type": "markdown", + "id": "3cb6fdc2-6362-40dd-a5e9-91bb23cd500c", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "## Output" + ] + }, + { + "cell_type": "markdown", + "id": "e7e25766-2ce9-4f72-a925-ee4bc4c5031b", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Send message to Slack" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d22aa13-1a2f-45c0-8fac-4f148828c629", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "slack.connect(slack_token).send(slack_channel, text=\"\", blocks=blocks)" + ] + }, + { + "cell_type": "markdown", + "id": "7bcec3e2-f77a-4053-9055-e3ffed6de3d9", + "metadata": { + "papermill": {}, + "tags": [] + }, + "source": [ + "### Add scheduler" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce43e5fb-323b-47ec-b308-8c320e426280", + "metadata": { + "papermill": {}, + "tags": [] + }, + "outputs": [], + "source": [ + "naas.scheduler.add(cron=cron)\n", + "\n", + "# naas.scheduler.delete()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": {}, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}