diff --git a/app-nest/package-lock.json b/app-nest/package-lock.json index 51a4bee..e7ef30b 100644 --- a/app-nest/package-lock.json +++ b/app-nest/package-lock.json @@ -21,6 +21,7 @@ "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", + "slack-block-builder": "^2.7.2", "typeorm": "^0.3.17" }, "devDependencies": { @@ -8247,6 +8248,11 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true }, + "node_modules/slack-block-builder": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/slack-block-builder/-/slack-block-builder-2.7.2.tgz", + "integrity": "sha512-EhoXDveGZWrHL0+AKnm3wu8n9K9usMjJGlbYLLwMSq62J7Qv+a4lO1iJI+NmCE04dm37d/3OlGKxBVoASnynog==" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/app-nest/package.json b/app-nest/package.json index c78f88d..29a5d91 100644 --- a/app-nest/package.json +++ b/app-nest/package.json @@ -40,6 +40,7 @@ "pg": "^8.11.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", + "slack-block-builder": "^2.7.2", "typeorm": "^0.3.17" }, "devDependencies": { diff --git a/app-nest/src/gui/dev/dev-tools.builder.ts b/app-nest/src/gui/dev/dev-tools.builder.ts new file mode 100644 index 0000000..1be0c51 --- /dev/null +++ b/app-nest/src/gui/dev/dev-tools.builder.ts @@ -0,0 +1,22 @@ +import { Injectable } from "@nestjs/common"; +import { Actions, Button, Context, Divider, Header } from "slack-block-builder"; +import BoltActions from "../../bolt/enums/bolt-actions.enum"; + +@Injectable() +export class DevToolsBuilder { + build() { + return [ + Header({ text: ":wrench: Developer Tools" }), + Actions().elements( + Button({ + text: ":recycle: Sync Users", + actionId: BoltActions.SYNC_USERS, + }), + ), + Context().elements( + "In development environment, users are not synchronized between local database and Slack on app start to avoid running into API rate limits due to hot-reloads. Sync users manually when necessary.", + ), + Divider(), + ]; + } +} diff --git a/app-nest/src/gui/dev/dev-tools.module.ts b/app-nest/src/gui/dev/dev-tools.module.ts new file mode 100644 index 0000000..8aefa0a --- /dev/null +++ b/app-nest/src/gui/dev/dev-tools.module.ts @@ -0,0 +1,5 @@ +import { Module } from "@nestjs/common"; +import { DevToolsBuilder } from "./dev-tools.builder"; + +@Module({ providers: [DevToolsBuilder], exports: [DevToolsBuilder] }) +export class DevToolsModule {} diff --git a/app-nest/src/gui/dev/dev-tools.ts b/app-nest/src/gui/dev/dev-tools.ts deleted file mode 100644 index eb17914..0000000 --- a/app-nest/src/gui/dev/dev-tools.ts +++ /dev/null @@ -1,38 +0,0 @@ -import BoltActions from "../../bolt/enums/bolt-actions.enum"; - -const devTools = [ - { - type: "header", - text: { - type: "plain_text", - text: ":wrench: Developer Tools", - }, - }, - { - type: "actions", - elements: [ - { - type: "button", - text: { - type: "plain_text", - text: ":recycle: Sync Users", - }, - action_id: BoltActions.SYNC_USERS, - }, - ], - }, - { - type: "context", - elements: [ - { - type: "plain_text", - text: "In development environment, users are not synchronized between local database and Slack on app start to avoid running into API rate limits due to hot-reloads. Sync users manually when necessary.", - }, - ], - }, - { - type: "divider", - }, -]; - -export default devTools; diff --git a/app-nest/src/gui/gui.module.ts b/app-nest/src/gui/gui.module.ts index afc423d..9e0fc81 100644 --- a/app-nest/src/gui/gui.module.ts +++ b/app-nest/src/gui/gui.module.ts @@ -1,11 +1,7 @@ import { Module } from "@nestjs/common"; -import { UserModule } from "../entities/user/user.module"; -import { UserService } from "../entities/user/user.service"; -import { HomeTabController } from "./tabs/home/home-tab.controller"; +import { HomeTabModule } from "./tabs/home/home-tab.module"; @Module({ - imports: [UserModule], - providers: [UserService], - controllers: [HomeTabController], + imports: [HomeTabModule], }) export class GuiModule {} diff --git a/app-nest/src/gui/tabs/home/day-list-item.blocks.ts b/app-nest/src/gui/tabs/home/day-list-item.blocks.ts deleted file mode 100644 index c8d5fc4..0000000 --- a/app-nest/src/gui/tabs/home/day-list-item.blocks.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Dayjs } from "dayjs"; -import BoltActions from "../../../bolt/enums/bolt-actions.enum"; - -type DayListItemProps = { - date: Dayjs; -}; - -const getDayListItemBlocks = ({ date }: DayListItemProps) => [ - { - type: "header", - text: { - type: "plain_text", - text: date.format("dd D.M."), - }, - }, - { - type: "actions", - elements: [ - { - type: "button", - text: { - type: "plain_text", - text: "Toimistolla", - }, - style: "primary", - action_id: BoltActions.SET_OFFICE_PRESENCE, - value: date.toISOString(), - }, - { - type: "button", - text: { - type: "plain_text", - text: "Etänä", - }, - action_id: BoltActions.SET_REMOTE_PRESENCE, - value: date.toISOString(), - }, - { - action_id: BoltActions.SELECT_OFFICE_FOR_DATE, - type: "static_select", - placeholder: { - type: "plain_text", - text: "Valitse toimipiste", - }, - initial_option: { - text: { - type: "plain_text", - text: "Helsinki", - }, - value: JSON.stringify({ value: "hki", date: date.toISOString() }), - }, - options: [ - { - text: { - type: "plain_text", - text: "Helsinki", - }, - value: JSON.stringify({ value: "hki", date: date.toISOString() }), - }, - { - text: { - type: "plain_text", - text: "Tampere", - }, - value: JSON.stringify({ value: "tre", date: date.toISOString() }), - }, - ], - }, - { - type: "overflow", - options: [ - { - text: { - type: "plain_text", - text: "Poista ilmoittautuminen", - }, - value: JSON.stringify({ - type: "remove_presence", - date: date.toISOString(), - }), - }, - ], - action_id: BoltActions.DAY_LIST_ITEM_OVERFLOW, - }, - ], - }, -]; - -export default getDayListItemBlocks; diff --git a/app-nest/src/gui/tabs/home/day-list-item.builder.ts b/app-nest/src/gui/tabs/home/day-list-item.builder.ts new file mode 100644 index 0000000..ad71e10 --- /dev/null +++ b/app-nest/src/gui/tabs/home/day-list-item.builder.ts @@ -0,0 +1,67 @@ +import { Injectable } from "@nestjs/common"; +import { Dayjs } from "dayjs"; +import { + Actions, + Button, + Header, + Option, + OverflowMenu, + StaticSelect, +} from "slack-block-builder"; +import BoltActions from "../../../bolt/enums/bolt-actions.enum"; + +type DayListItemProps = { + date: Dayjs; +}; + +@Injectable() +export class DayListItemBuilder { + build({ date }: DayListItemProps) { + const dateString = date.toISOString(); + + return [ + Header({ text: date.format("dd D.M.") }), + Actions().elements( + Button({ + text: "Toimistolla", + actionId: BoltActions.SET_OFFICE_PRESENCE, + value: dateString, + }), + Button({ + text: "Etänä", + actionId: BoltActions.SET_REMOTE_PRESENCE, + value: dateString, + }), + StaticSelect({ + placeholder: "Valitse toimipiste", + actionId: BoltActions.SELECT_OFFICE_FOR_DATE, + }) + .initialOption( + Option({ + text: "Helsinki", + value: JSON.stringify({ value: "hki", date }), + }), + ) + .options( + Option({ + text: "Helsinki", + value: JSON.stringify({ value: "hki", date }), + }), + Option({ + text: "Tampere", + value: JSON.stringify({ value: "tre", date }), + }), + ), + OverflowMenu({ actionId: BoltActions.DAY_LIST_ITEM_OVERFLOW }).options( + Option({ + text: "Poista ilmoittautuminen", + value: JSON.stringify({ + type: "remove_presence", + date, + }), + }), + ), + ), + ]; + } +} diff --git a/app-nest/src/gui/tabs/home/day-list.blocks.ts b/app-nest/src/gui/tabs/home/day-list.builder.ts similarity index 61% rename from app-nest/src/gui/tabs/home/day-list.blocks.ts rename to app-nest/src/gui/tabs/home/day-list.builder.ts index 388209c..cf7d869 100644 --- a/app-nest/src/gui/tabs/home/day-list.blocks.ts +++ b/app-nest/src/gui/tabs/home/day-list.builder.ts @@ -1,6 +1,7 @@ +import { Injectable } from "@nestjs/common"; import dayjs, { Dayjs } from "dayjs"; import { flatten } from "lodash"; -import getDayListItemBlocks from "./day-list-item.blocks"; +import { DayListItemBuilder } from "./day-list-item.builder"; /** * Get range of days from today (inclusive) for the next `len` working days (defined as Mon-Fri). @@ -24,10 +25,15 @@ const dayRange = (len: number, days: Dayjs[] = [], i = 0): Dayjs[] => { return dayRange(len, days, i + 1); }; -const getDayListBlocks = () => { - const dates = dayRange(14); - const blockLists = dates.map((date) => getDayListItemBlocks({ date })); - return flatten(blockLists); -}; +@Injectable() +export class DayListBuilder { + constructor(private dayListItemBuilder: DayListItemBuilder) {} -export default getDayListBlocks; + async build() { + const dates = dayRange(14); + const blockLists = dates.map((date) => + this.dayListItemBuilder.build({ date }), + ); + return flatten(blockLists); + } +} diff --git a/app-nest/src/gui/tabs/home/home-tab.builder.ts b/app-nest/src/gui/tabs/home/home-tab.builder.ts new file mode 100644 index 0000000..6f76b64 --- /dev/null +++ b/app-nest/src/gui/tabs/home/home-tab.builder.ts @@ -0,0 +1,19 @@ +import { Injectable } from "@nestjs/common"; +import { Header } from "slack-block-builder"; +import { DevToolsBuilder } from "../../dev/dev-tools.builder"; +import { DayListBuilder } from "./day-list.builder"; + +// TODO: Create interface for these kind of builder classes. +@Injectable() +export class HomeTabBuilder { + constructor( + private dayListBlocks: DayListBuilder, + private devToolsBuilder: DevToolsBuilder, + ) {} + + async build() { + const devTools = this.devToolsBuilder.build(); + const dayList = await this.dayListBlocks.build(); + return [...devTools, Header({ text: "Ilmoittautumiset" }), ...dayList]; + } +} diff --git a/app-nest/src/gui/tabs/home/home-tab.controller.ts b/app-nest/src/gui/tabs/home/home-tab.controller.ts index 6d8ac1b..cfc451a 100644 --- a/app-nest/src/gui/tabs/home/home-tab.controller.ts +++ b/app-nest/src/gui/tabs/home/home-tab.controller.ts @@ -1,28 +1,25 @@ import { Controller } from "@nestjs/common"; +import { HomeTab } from "slack-block-builder"; import BoltEvent from "../../../bolt/decorators/bolt-event.decorator"; import BoltEvents from "../../../bolt/enums/bolt-events.enum"; import { AppHomeOpenedArgs } from "../../../bolt/types/bolt-event-types"; -import { UserService } from "../../../entities/user/user.service"; -import getDayListBlocks from "./day-list.blocks"; -import getHomeTabBlocks from "./home-tab.view"; +import { HomeTabBuilder } from "./home-tab.builder"; @Controller() export class HomeTabController { - constructor(private userService: UserService) {} + constructor(private homeTabBlocks: HomeTabBuilder) {} @BoltEvent(BoltEvents.APP_HOME_OPENED) async getView({ event, client, logger }: AppHomeOpenedArgs) { - const users = await this.userService.findAll(); - const { slackId } = users[0]; - getDayListBlocks(); + const blocks = await this.homeTabBlocks.build(); + const view = HomeTab() + .blocks(...blocks) + .buildToObject(); try { const result = await client.views.publish({ user_id: event.user, - view: { - type: "home", - blocks: getHomeTabBlocks(), - }, + view, }); logger.debug(result); diff --git a/app-nest/src/gui/tabs/home/home-tab.module.ts b/app-nest/src/gui/tabs/home/home-tab.module.ts new file mode 100644 index 0000000..2c7f39b --- /dev/null +++ b/app-nest/src/gui/tabs/home/home-tab.module.ts @@ -0,0 +1,13 @@ +import { Module } from "@nestjs/common"; +import { DevToolsModule } from "../../dev/dev-tools.module"; +import { DayListItemBuilder } from "./day-list-item.builder"; +import { DayListBuilder } from "./day-list.builder"; +import { HomeTabBuilder } from "./home-tab.builder"; +import { HomeTabController } from "./home-tab.controller"; + +@Module({ + imports: [DevToolsModule], + providers: [HomeTabBuilder, DayListBuilder, DayListItemBuilder], + controllers: [HomeTabController], +}) +export class HomeTabModule {} diff --git a/app-nest/src/gui/tabs/home/home-tab.view.ts b/app-nest/src/gui/tabs/home/home-tab.view.ts deleted file mode 100644 index 22b7f6f..0000000 --- a/app-nest/src/gui/tabs/home/home-tab.view.ts +++ /dev/null @@ -1,16 +0,0 @@ -import devTools from "../../dev/dev-tools"; -import getDayListBlocks from "./day-list.blocks"; - -const getHomeTabBlocks = () => [ - ...devTools, - { - type: "header", - text: { - type: "plain_text", - text: "Ilmoittautumiset", - }, - }, - ...getDayListBlocks(), -]; - -export default getHomeTabBlocks;