From 59648785ab87ad7b4d540215be7d07651734503e Mon Sep 17 00:00:00 2001 From: Zishan Ahmad Date: Wed, 6 Mar 2024 11:58:34 +0100 Subject: [PATCH 1/4] initial commit --- NotionApp.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/NotionApp.ts b/NotionApp.ts index aa77b54..31cb144 100644 --- a/NotionApp.ts +++ b/NotionApp.ts @@ -1,3 +1,4 @@ +//Started working on Update DB Record Feature import { IAppAccessors, IAppInstallationContext, From 8febf8d298e484ed5468f6f0050131dcf9218bf6 Mon Sep 17 00:00:00 2001 From: Zishan Ahmad Date: Sat, 9 Mar 2024 00:38:04 +0530 Subject: [PATCH 2/4] added command to update record added help created update record modal added info fectch of selected db --- enum/CommandParam.ts | 4 +- enum/messages.ts | 3 +- enum/modals/NotionUpdateRecord.ts | 17 +++++ src/commands/CommandUtility.ts | 18 ++++- src/handlers/ExecuteBlockActionHandler.ts | 84 +++++++++++++++++++++++ src/handlers/ExecuteViewSubmitHandler.ts | 35 +++++++++- src/handlers/Handler.ts | 62 +++++++++++++++++ src/modals/updateRecordModal.ts | 84 +++++++++++++++++++++++ 8 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 enum/modals/NotionUpdateRecord.ts create mode 100644 src/modals/updateRecordModal.ts diff --git a/enum/CommandParam.ts b/enum/CommandParam.ts index a3a5282..20cee27 100644 --- a/enum/CommandParam.ts +++ b/enum/CommandParam.ts @@ -7,9 +7,11 @@ export enum CommandParam { WORKSPACE = "workspace", WS = "ws", SHARE = "share", - VIEW = "view" + VIEW = "view", + UPDATE = "update" } export enum SubCommandParam { DATABASE = "db", + RECORD = "record" } diff --git a/enum/messages.ts b/enum/messages.ts index 851a876..15e021a 100644 --- a/enum/messages.ts +++ b/enum/messages.ts @@ -5,7 +5,8 @@ export enum Messages { • use \`/notion create\` to create page or record • use \`/notion create db\` to create database • use \`/notion workspace\` to change workspace - • use \`/notion share\` to share pages + • use \`/notion share\` to share pages + • use \`/notion update record\` to update database record `, HELPER_TEXT = `Need some help with \`/notion\`?`, } diff --git a/enum/modals/NotionUpdateRecord.ts b/enum/modals/NotionUpdateRecord.ts new file mode 100644 index 0000000..90277b3 --- /dev/null +++ b/enum/modals/NotionUpdateRecord.ts @@ -0,0 +1,17 @@ +export enum NotionUpdateRecord { + VIEW_ID = "notion-update-record-view-id", + TITLE = "Update Database Record", + SEARCH_DB_ACTION_ID = "notion-update-record-search-database-component-action-id", + TITLE_BLOCK = "title-database-block-id", + TITLE_ACTION = "title-database-action-id", + PROPERTY_SELECTED_BLOCK_ELEMENT = "property-selected-element-update-record-db-block-id", + ADD_PROPERTY_ACTION = "add-property-notion-update-record-action-id", + ADD_PROPERTY_BLOCK = "add-property-notion-update-record-block-id", + ADD_PROPERTY_BUTTON_TEXT = "Add Property", + UPDATE_RECORD = "Update", + UPDATE_RECORD_ACTION = "update-record-action-id", + UPDATE_RECORD_BLOCK = "update-record-block-id", + CLOSE = "Close", + CLOSE_ACTION = "close-update-record-action-id", + CLOSE_BLOCK = "close-update-record-block-id", +} \ No newline at end of file diff --git a/src/commands/CommandUtility.ts b/src/commands/CommandUtility.ts index eb8c1a1..60de9ab 100644 --- a/src/commands/CommandUtility.ts +++ b/src/commands/CommandUtility.ts @@ -123,7 +123,7 @@ export class CommandUtility implements ICommandUtility { await handler.shareNotionPage(); break; } - case CommandParam.VIEW : { + case CommandParam.VIEW: { await handler.viewNotionTable(); break; } @@ -156,6 +156,22 @@ export class CommandUtility implements ICommandUtility { ); break; } + + case CommandParam.UPDATE: { + if (subparam.toLowerCase() === SubCommandParam.RECORD) { + await handler.updateDatabaseRecord(); + return; + } + + await sendHelperNotification( + this.read, + this.modify, + this.sender, + this.room + ); + + break; + } default: { await sendHelperNotification( this.read, diff --git a/src/handlers/ExecuteBlockActionHandler.ts b/src/handlers/ExecuteBlockActionHandler.ts index 01bfec3..008346f 100644 --- a/src/handlers/ExecuteBlockActionHandler.ts +++ b/src/handlers/ExecuteBlockActionHandler.ts @@ -51,6 +51,8 @@ import { getPropertiesIdsObject } from "../helper/getPropertiesIdsObject"; import { SharePage } from "../../enum/modals/SharePage"; import { NotionTable } from "../../enum/modals/NotionTable"; import { SendMessagePage } from "../../enum/modals/SendMessagePage"; +import { NotionUpdateRecord } from "../../enum/modals/NotionUpdateRecord"; +import { updateRecordModal } from "../modals/updateRecordModal"; export class ExecuteBlockActionHandler { private context: UIKitBlockInteractionContext; @@ -167,6 +169,14 @@ export class ExecuteBlockActionHandler { ); break; } + + case NotionUpdateRecord.SEARCH_DB_ACTION_ID:{ + return this.handleSearchDbUpdateRecord( + modalInteraction, + oAuth2Storage, + roomInteractionStorage + ) + } case SendMessagePage.ACTION_ID: case SharePage.ACTION_ID: { return this.handleSelectPageAction(); @@ -1043,4 +1053,78 @@ export class ExecuteBlockActionHandler { errors: {}, }); } + + private async handleSearchDbUpdateRecord( + modalInteraction: ModalInteractionStorage, + oAuth2Storage: OAuth2Storage, + roomInteractionStorage: RoomInteractionStorage + ): Promise { + const { value, user } = this.context.getInteractionData(); + + const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); + const roomId = await roomInteractionStorage.getInteractionRoomId(); + const room = (await this.read.getRoomReader().getById(roomId)) as IRoom; + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + user, + this.read, + this.modify, + room + ); + + return this.context.getInteractionResponder().errorResponse(); + } + + if (!value) { + return this.context.getInteractionResponder().errorResponse(); + } + + let Object: IDatabase = JSON.parse(value); + + const database = Object as IDatabase; + + const databaseId = database.parent.database_id; + const { NotionSdk } = this.app.getUtils(); + const { access_token } = tokenInfo; + + const properties = await NotionSdk.retrieveDatabase( + access_token, + databaseId + ); + + if (properties instanceof Error) { + this.app.getLogger().error(properties.message); + return this.context.getInteractionResponder().errorResponse(); + } + + await modalInteraction.storeInputElementState( + NotionUpdateRecord.SEARCH_DB_ACTION_ID, + properties + ); + + await this.storeAllElements(properties, tokenInfo, modalInteraction); + + const modal = await updateRecordModal( + this.app, + user, + this.read, + this.persistence, + this.modify, + room, + modalInteraction, + tokenInfo, + database + ); + + if (modal instanceof Error) { + this.app.getLogger().error(modal.message); + return this.context.getInteractionResponder().errorResponse(); + } + + return this.context + .getInteractionResponder() + .updateModalViewResponse(modal); + } } diff --git a/src/handlers/ExecuteViewSubmitHandler.ts b/src/handlers/ExecuteViewSubmitHandler.ts index 8abe7ee..1a969c3 100644 --- a/src/handlers/ExecuteViewSubmitHandler.ts +++ b/src/handlers/ExecuteViewSubmitHandler.ts @@ -52,6 +52,7 @@ import { SendMessagePage } from "../../enum/modals/SendMessagePage"; import { NotionTable } from "../../enum/modals/NotionTable"; import { SearchDatabaseComponent } from "../../enum/modals/common/SearchDatabaseComponent"; import { table } from "table"; +import { NotionUpdateRecord } from "../../enum/modals/NotionUpdateRecord"; export class ExecuteViewSubmitHandler { private context: UIKitViewSubmitInteractionContext; @@ -139,6 +140,14 @@ export class ExecuteViewSubmitHandler { ); break; } + case NotionUpdateRecord.VIEW_ID: { + return this.handleUpdateRecord( + room, + oAuth2Storage, + modalInteraction + ); + break; + } default: { } } @@ -341,7 +350,6 @@ export class ExecuteViewSubmitHandler { const parentType: string = parent.type; if (parentType.includes(NotionObjectTypes.PAGE_ID)) { - return this.handleCreationOfPage( tokenInfo, room, @@ -350,7 +358,7 @@ export class ExecuteViewSubmitHandler { Objects as IPage ); } - + return this.handleCreationOfRecord( tokenInfo, room, @@ -998,4 +1006,27 @@ export class ExecuteViewSubmitHandler { return this.context.getInteractionResponder().successResponse(); } + + private async handleUpdateRecord( + room: IRoom, + oAuth2Storage: OAuth2Storage, + modalInteraction: ModalInteractionStorage + ): Promise { + const { view, user } = this.context.getInteractionData(); + + const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + user, + this.read, + this.modify, + room + ); + return this.context.getInteractionResponder().errorResponse(); + } + + return this.context.getInteractionResponder().successResponse(); + } } diff --git a/src/handlers/Handler.ts b/src/handlers/Handler.ts index ad3b741..c386aae 100644 --- a/src/handlers/Handler.ts +++ b/src/handlers/Handler.ts @@ -20,6 +20,7 @@ import { createCommentContextualBar } from "../modals/createCommentContextualBar import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; import { createPageOrRecordModal } from "../modals/createPageOrRecordModal"; import { changeWorkspaceModal } from "../modals/changeWorkspaceModal"; +import { updateRecordModal } from "../modals/updateRecordModal"; import { NotionWorkspace } from "../../enum/modals/NotionWorkspace"; import { SearchPageAndDatabase } from "../../enum/modals/common/SearchPageAndDatabaseComponent"; import { NotionOwnerType } from "../../enum/Notion"; @@ -33,6 +34,7 @@ import { SendMessagePage } from "../../enum/modals/SendMessagePage"; import { sendMessagePageModal } from "../modals/sendMessagePageModal"; import { NotionTable } from "../../enum/modals/NotionTable"; import { viewNotionTableModal } from "../modals/viewNotionTableModal"; +import { NotionUpdateRecord } from "../../enum/modals/NotionUpdateRecord"; export class Handler implements IHandler { public app: NotionApp; @@ -515,4 +517,64 @@ export class Handler implements IHandler { .openSurfaceView(modal, { triggerId }, this.sender); } } + + + public async updateDatabaseRecord( + ): Promise { + const userId = this.sender.id; + const roomId = this.room.id; + const tokenInfo = await this.oAuth2Storage.getCurrentWorkspace(userId); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + this.sender, + this.read, + this.modify, + this.room + ); + return; + } + + const persistenceRead = this.read.getPersistenceReader(); + const modalInteraction = new ModalInteractionStorage( + this.persis, + persistenceRead, + userId, + NotionUpdateRecord.VIEW_ID + ); + + const { workspace_id, access_token } = tokenInfo; + + await Promise.all([ + this.roomInteractionStorage.storeInteractionRoomId(roomId), + modalInteraction.clearAllInteractionActionId(), + ]); + + const modal = await updateRecordModal( + this.app, + this.sender, + this.read, + this.persis, + this.modify, + this.room, + modalInteraction, + tokenInfo + ); + + if (modal instanceof Error) { + this.app.getLogger().error(modal.message); + return; + } + + const triggerId = this.triggerId; + + if (triggerId) { + await this.modify + .getUiController() + .openSurfaceView(modal, { triggerId }, this.sender); + } + + return; + } } \ No newline at end of file diff --git a/src/modals/updateRecordModal.ts b/src/modals/updateRecordModal.ts new file mode 100644 index 0000000..963d8c6 --- /dev/null +++ b/src/modals/updateRecordModal.ts @@ -0,0 +1,84 @@ +import { + IModify, + IPersistence, + IRead, + IUIKitSurfaceViewParam, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { + ButtonStyle, + UIKitSurfaceType, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { TextObjectType, Block } from "@rocket.chat/ui-kit"; +import { Error } from "../../errors/Error"; +import { NotionApp } from "../../NotionApp"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { ModalInteractionStorage } from "../storage/ModalInteraction"; +import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; +import { getConnectPreview } from "../helper/getConnectLayout"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { searchDatabaseComponent } from "./common/searchDatabaseComponent"; +import { NotionUpdateRecord } from "../../enum/modals/NotionUpdateRecord"; +import { IDatabase } from "../../definition/lib/INotion"; + +export async function updateRecordModal( + app: NotionApp, + user: IUser, + read: IRead, + persistence: IPersistence, + modify: IModify, + room: IRoom, + modalInteraction: ModalInteractionStorage, + tokenInfo: ITokenInfo, + parent?: IDatabase +): Promise { + const { elementBuilder, blockBuilder } = app.getUtils(); + const divider = blockBuilder.createDividerBlock(); + const connectBlock = getConnectPreview(app.getID(), tokenInfo); + + const searchForDatabaseComponent = await searchDatabaseComponent( + app, + modalInteraction, + tokenInfo, + NotionUpdateRecord.SEARCH_DB_ACTION_ID + ); + + if (searchForDatabaseComponent instanceof Error) { + return searchForDatabaseComponent; + } + + const blocks: Block[] = [connectBlock, searchForDatabaseComponent]; + + const submit = elementBuilder.addButton( + { text: NotionUpdateRecord.UPDATE_RECORD, style: ButtonStyle.PRIMARY }, + { + actionId: NotionUpdateRecord.UPDATE_RECORD_ACTION, + blockId: NotionUpdateRecord.UPDATE_RECORD_BLOCK, + } + ); + + const close = elementBuilder.addButton( + { text: NotionUpdateRecord.CLOSE, style: ButtonStyle.DANGER }, + { + actionId: NotionUpdateRecord.CLOSE_ACTION, + blockId: NotionUpdateRecord.CLOSE_BLOCK, + } + ); + + + if (parent) { + app.getLogger().debug(parent); + // Handle fetching record of DB and UI Changes + } + + return { + id: NotionUpdateRecord.VIEW_ID, + type: UIKitSurfaceType.MODAL, + title: { + type: TextObjectType.MRKDWN, + text: NotionUpdateRecord.TITLE, + }, + blocks, + close, + submit, + }; +} From f6a0e41efb56f08f0e40689f869c696c82be1f17 Mon Sep 17 00:00:00 2001 From: Zishan Ahmad Date: Thu, 11 Apr 2024 22:18:27 +0530 Subject: [PATCH 3/4] added selected db preview block --- src/modals/updateRecordModal.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/modals/updateRecordModal.ts b/src/modals/updateRecordModal.ts index 963d8c6..a913892 100644 --- a/src/modals/updateRecordModal.ts +++ b/src/modals/updateRecordModal.ts @@ -19,6 +19,7 @@ import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; import { searchDatabaseComponent } from "./common/searchDatabaseComponent"; import { NotionUpdateRecord } from "../../enum/modals/NotionUpdateRecord"; import { IDatabase } from "../../definition/lib/INotion"; +import { getSelectDatabaseLayout } from "../helper/getSelectDatabaseLayout"; export async function updateRecordModal( app: NotionApp, @@ -32,8 +33,7 @@ export async function updateRecordModal( parent?: IDatabase ): Promise { const { elementBuilder, blockBuilder } = app.getUtils(); - const divider = blockBuilder.createDividerBlock(); - const connectBlock = getConnectPreview(app.getID(), tokenInfo); + const appId = app.getID(); const searchForDatabaseComponent = await searchDatabaseComponent( app, @@ -46,7 +46,13 @@ export async function updateRecordModal( return searchForDatabaseComponent; } - const blocks: Block[] = [connectBlock, searchForDatabaseComponent]; + const blocks: Block[] = []; + if(!parent){ + const connectBlock = getConnectPreview(app.getID(), tokenInfo); + blocks.push(connectBlock); + blocks.push(searchForDatabaseComponent); + } + const submit = elementBuilder.addButton( { text: NotionUpdateRecord.UPDATE_RECORD, style: ButtonStyle.PRIMARY }, @@ -67,7 +73,14 @@ export async function updateRecordModal( if (parent) { app.getLogger().debug(parent); - // Handle fetching record of DB and UI Changes + const { info } = parent; + const SelectedDatabaseLayout = getSelectDatabaseLayout( + appId, + tokenInfo, + info + ); + + blocks.push(SelectedDatabaseLayout); } return { From ab277b13dbd847d496148b5ab749f5408cf36ff4 Mon Sep 17 00:00:00 2001 From: Zishan Ahmad Date: Fri, 12 Apr 2024 00:27:25 +0530 Subject: [PATCH 4/4] added selection of records --- enum/modals/common/SearchRecordComponent.ts | 6 ++ src/modals/common/searchRecordComponent.ts | 86 +++++++++++++++++++++ src/modals/updateRecordModal.ts | 23 ++++-- 3 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 enum/modals/common/SearchRecordComponent.ts create mode 100644 src/modals/common/searchRecordComponent.ts diff --git a/enum/modals/common/SearchRecordComponent.ts b/enum/modals/common/SearchRecordComponent.ts new file mode 100644 index 0000000..040fbc8 --- /dev/null +++ b/enum/modals/common/SearchRecordComponent.ts @@ -0,0 +1,6 @@ +export enum SearchRecordComponent { + PLACEHOLDER = "Select Record", + BLOCK_ID = "search-record-component-block-id", + ACTION_ID = "search-record-component-action-id", + LABEL = "Record Title *", +} diff --git a/src/modals/common/searchRecordComponent.ts b/src/modals/common/searchRecordComponent.ts new file mode 100644 index 0000000..5fda6ae --- /dev/null +++ b/src/modals/common/searchRecordComponent.ts @@ -0,0 +1,86 @@ +import { NotionApp } from "../../../NotionApp"; +import { InputBlock } from "@rocket.chat/ui-kit"; +import { Error } from "../../../errors/Error"; +import { StaticSelectOptionsParam } from "../../../definition/ui-kit/Element/IStaticSelectElement"; +import { ModalInteractionStorage } from "../../storage/ModalInteraction"; +import { IDatabase } from "../../../definition/lib/INotion"; +import { ITokenInfo } from "../../../definition/authorization/IOAuth2Storage"; +import { Modals } from "../../../enum/modals/common/Modals"; +import { NotionObjectTypes } from "../../../enum/Notion"; +import { SearchRecordComponent } from "../../../enum/modals/common/SearchRecordComponent"; +export async function searchRecordComponent( + app: NotionApp, + modalInteraction: ModalInteractionStorage, + tokenInfo: ITokenInfo, + properties: any, + database: IDatabase, + actionId: string +): Promise { + const { NotionSdk, elementBuilder, blockBuilder } = app.getUtils(); + const { access_token, workspace_id } = tokenInfo; + let Records; + let recordTitles: string[] = []; + if (!Records) { + let databaseTitle; + const columns = Object.keys(properties); + const firstColumn = columns[0]; + const lastColumn = columns[columns.length - 1]; + + if (properties[firstColumn]?.type == NotionObjectTypes.TITLE) { + databaseTitle = firstColumn; + } else { + databaseTitle = lastColumn; + } + + const database_id = database.parent.database_id; + const response = await NotionSdk.queryDatabasePages( + access_token, + database_id + ); + + const databaseTitleIndex: number = response[0].findIndex( + (title) => title === databaseTitle + ); + + if (databaseTitleIndex !== -1) { + if (!(response instanceof Error)) { + for (let i = 1; i < response.length; i++) { + recordTitles.push(response[i][databaseTitleIndex]); + } + } + } + + console + + if (Records instanceof Error) { + return Records; + } + } + + const options: StaticSelectOptionsParam = recordTitles.map((item) => { + const text: string = item; + const value = JSON.stringify(item); + + return { + text, + value, + }; + }); + + const dropDownOption = elementBuilder.createDropDownOptions(options); + const dropDown = elementBuilder.addDropDown( + { + placeholder: SearchRecordComponent.PLACEHOLDER, + options: dropDownOption, + dispatchActionConfig: [Modals.dispatchActionConfigOnSelect], + }, + { blockId: SearchRecordComponent.BLOCK_ID, actionId } + ); + const inputBlock = blockBuilder.createInputBlock({ + text: SearchRecordComponent.LABEL, + element: dropDown, + optional: false, + }); + + return inputBlock; +} diff --git a/src/modals/updateRecordModal.ts b/src/modals/updateRecordModal.ts index a913892..4f8ecd1 100644 --- a/src/modals/updateRecordModal.ts +++ b/src/modals/updateRecordModal.ts @@ -17,6 +17,7 @@ import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; import { getConnectPreview } from "../helper/getConnectLayout"; import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; import { searchDatabaseComponent } from "./common/searchDatabaseComponent"; +import { searchRecordComponent } from "./common/searchRecordComponent"; import { NotionUpdateRecord } from "../../enum/modals/NotionUpdateRecord"; import { IDatabase } from "../../definition/lib/INotion"; import { getSelectDatabaseLayout } from "../helper/getSelectDatabaseLayout"; @@ -47,12 +48,11 @@ export async function updateRecordModal( } const blocks: Block[] = []; - if(!parent){ + if (!parent) { const connectBlock = getConnectPreview(app.getID(), tokenInfo); blocks.push(connectBlock); - blocks.push(searchForDatabaseComponent); } - + blocks.push(searchForDatabaseComponent); const submit = elementBuilder.addButton( { text: NotionUpdateRecord.UPDATE_RECORD, style: ButtonStyle.PRIMARY }, @@ -70,17 +70,28 @@ export async function updateRecordModal( } ); - if (parent) { - app.getLogger().debug(parent); const { info } = parent; const SelectedDatabaseLayout = getSelectDatabaseLayout( appId, tokenInfo, info ); - blocks.push(SelectedDatabaseLayout); + let properties = await modalInteraction.getInputElementState( + NotionUpdateRecord.SEARCH_DB_ACTION_ID + ); + const searchForRecordComponent = await searchRecordComponent( + app, + modalInteraction, + tokenInfo, + properties, + parent, + NotionUpdateRecord.SEARCH_DB_ACTION_ID + ); + + if (!(searchForRecordComponent instanceof Error)) + blocks.push(searchForRecordComponent); } return {