From ad49a19f04ee38d848f67228d0ccc2090d099f8f Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Wed, 15 Nov 2023 02:33:03 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20484:=20NotificationHubs=E3=81=AE?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E3=83=95=E3=82=A9=E3=83=BC=E3=83=9E=E3=83=83?= =?UTF-8?q?=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task2820: NotificationHubsの通知フォーマット](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2820) - NotificationHubsで通知を行う処理を修正 - 通知内容を変更 - デスクトップ側でレンダリングを行うため、こちらからは必要なデータを送信する - 通知処理を読んでいる箇所を修正 - タスクキャンセル - チェックアウト候補変更 - 音声ファイルアップロード完了(タスク作成) ## レビューポイント - taskService内で通知をする箇所が複数あったのでプライベートメソッドとして切り出したが、fileServiceの通知処理もまとめたほうが良いか。 - まとめる場合は、どこに切り出すか。 - sendNotify(今回作成したプライベートメソッド)に、TasksRepositoryServiceやUserGroupsRepositoryServiceを引数に追加して共通関数としてCommonのどこかに配置する? - notificationhub.service.tsのnotifyメソッドの中で通知内容のための情報取得~通知内容作成まで全部行うようにする? ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば --- dictation_server/package-lock.json | 107 ++++++++++------- dictation_server/package.json | 2 +- dictation_server/src/common/notify/code.ts | 13 -- .../src/common/notify/makeNotifyMessage.ts | 7 -- dictation_server/src/common/notify/message.ts | 6 - .../src/common/notify/types/types.ts | 11 +- .../src/features/files/files.service.spec.ts | 31 ++++- .../src/features/files/files.service.ts | 12 +- .../src/features/tasks/tasks.service.spec.ts | 48 +++++++- .../src/features/tasks/tasks.service.ts | 112 +++++++++--------- .../src/features/tasks/test/utility.ts | 3 + .../notificationhub.service.ts | 33 ++++-- 12 files changed, 234 insertions(+), 151 deletions(-) delete mode 100644 dictation_server/src/common/notify/code.ts delete mode 100644 dictation_server/src/common/notify/makeNotifyMessage.ts delete mode 100644 dictation_server/src/common/notify/message.ts diff --git a/dictation_server/package-lock.json b/dictation_server/package-lock.json index da9ec43..5e2ef69 100644 --- a/dictation_server/package-lock.json +++ b/dictation_server/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@azure/identity": "^3.1.3", "@azure/keyvault-secrets": "^4.6.0", - "@azure/notification-hubs": "^1.0.2", + "@azure/notification-hubs": "^1.0.3", "@azure/storage-blob": "^12.14.0", "@microsoft/microsoft-graph-client": "^3.0.5", "@nestjs/axios": "^0.1.0", @@ -341,6 +341,22 @@ "openapi-types": ">=7" } }, + "node_modules/@azure-rest/core-client": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-1.1.4.tgz", + "integrity": "sha512-RUIQOA8T0WcbNlddr8hjl2MuC5GVRqmMwPXqBVsgvdKesLy+eg3y/6nf3qe2fvcJMI1gF6VtgU5U4hRaR4w4ag==", + "dependencies": { + "@azure/abort-controller": "^1.1.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-rest-pipeline": "^1.5.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@azure/abort-controller": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", @@ -353,15 +369,16 @@ } }, "node_modules/@azure/core-auth": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", - "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", + "integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==", "dependencies": { "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.1.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/@azure/core-client": { @@ -443,11 +460,12 @@ } }, "node_modules/@azure/core-lro": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.1.tgz", - "integrity": "sha512-JHQy/bA3NOz2WuzOi5zEk6n/TJdAropupxUT521JIJvW7EXV2YN2SFYZrf/2RHeD28QAClGdynYadZsbmP+nyQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.4.tgz", + "integrity": "sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==", "dependencies": { "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.2.0", "@azure/logger": "^1.0.0", "tslib": "^2.2.0" }, @@ -467,23 +485,22 @@ } }, "node_modules/@azure/core-rest-pipeline": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.2.tgz", - "integrity": "sha512-e3WzAsRKLor5EgK2bQqR1OY5D7VBqzORHtlqtygZZQGCYOIBsynqrZBa8MFD1Ue9r8TPtofOLditalnlQHS45Q==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.2.tgz", + "integrity": "sha512-wLLJQdL4v1yoqYtEtjKNjf8pJ/G/BqVomAWxcKOR1KbZJyCEnCv04yks7Y1NhJ3JzxbDs307W67uX0JzklFdCg==", "dependencies": { "@azure/abort-controller": "^1.0.0", "@azure/core-auth": "^1.4.0", "@azure/core-tracing": "^1.0.1", - "@azure/core-util": "^1.0.0", + "@azure/core-util": "^1.3.0", "@azure/logger": "^1.0.0", "form-data": "^4.0.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", - "tslib": "^2.2.0", - "uuid": "^8.3.0" + "tslib": "^2.2.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@azure/core-tracing": { @@ -498,23 +515,23 @@ } }, "node_modules/@azure/core-util": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz", - "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.6.1.tgz", + "integrity": "sha512-h5taHeySlsV9qxuK64KZxy4iln1BtMYlNt5jbuEFN3UFSAd1EwKg/Gjl5a6tZ/W8t6li3xPnutOx7zbDyXnPmQ==", "dependencies": { "@azure/abort-controller": "^1.0.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@azure/core-xml": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.3.3.tgz", - "integrity": "sha512-Go/xGz7nGqVINsD9O7gOfe8uiR1S+IFcw9WTUPJHSzoFT6F5ZWjXIIlSikLZm77TtmxzXGnQYjjiZIoIZ4x14A==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.3.4.tgz", + "integrity": "sha512-B1xI79Ur/u+KR69fGTcsMNj8KDjBSqAy0Ys6Byy4Qm1CqoUy7gCT5A7Pej0EBWRskuH6bpCwrAnosfmQEalkcg==", "dependencies": { - "fast-xml-parser": "^4.0.8", + "fast-xml-parser": "^4.2.4", "tslib": "^2.2.0" }, "engines": { @@ -628,25 +645,31 @@ } }, "node_modules/@azure/notification-hubs": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@azure/notification-hubs/-/notification-hubs-1.0.2.tgz", - "integrity": "sha512-INtgq8uFQpncwbKm4It8M0GkKIePNDNybhuXs4cQPf5H0i9CbfFEt2c6LtT1AdEzbWfUhjsmU5y0p3YDmecwwg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@azure/notification-hubs/-/notification-hubs-1.0.3.tgz", + "integrity": "sha512-IWzIO2nKwM+9CWgnTsMyZhXxR7q3n3/aXpJiqUn1U3xHXTtHVE+joZ/AL9fCZTEBVB1PQvbrjp8VZvwEptqJ/Q==", "dependencies": { + "@azure-rest/core-client": "^1.1.4", "@azure/abort-controller": "^1.1.0", - "@azure/core-client": "^1.6.1", - "@azure/core-lro": "^2.4.0", - "@azure/core-paging": "^1.3.0", - "@azure/core-rest-pipeline": "^1.8.1", + "@azure/core-auth": "^1.5.0", + "@azure/core-lro": "^2.5.4", + "@azure/core-paging": "^1.5.0", + "@azure/core-rest-pipeline": "^1.12.2", "@azure/core-tracing": "^1.0.1", - "@azure/core-util": "^1.3.0", - "@azure/core-xml": "^1.3.1", - "@azure/logger": "^1.0.3", - "tslib": "^2.4.0" + "@azure/core-util": "^1.6.1", + "@azure/core-xml": "^1.3.4", + "@azure/logger": "^1.0.4", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, + "node_modules/@azure/notification-hubs/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/@azure/storage-blob": { "version": "12.14.0", "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.14.0.tgz", @@ -5899,17 +5922,17 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "node_modules/fast-xml-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.6.tgz", - "integrity": "sha512-Xo1qV++h/Y3Ng8dphjahnYe+rGHaaNdsYOBWL9Y9GCPKpNKilJtilvWkLcI9f9X2DoKTLsZsGYAls5+JL5jfLA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz", + "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==", "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], "dependencies": { diff --git a/dictation_server/package.json b/dictation_server/package.json index 415e65c..1afac08 100644 --- a/dictation_server/package.json +++ b/dictation_server/package.json @@ -31,7 +31,7 @@ "dependencies": { "@azure/identity": "^3.1.3", "@azure/keyvault-secrets": "^4.6.0", - "@azure/notification-hubs": "^1.0.2", + "@azure/notification-hubs": "^1.0.3", "@azure/storage-blob": "^12.14.0", "@microsoft/microsoft-graph-client": "^3.0.5", "@nestjs/axios": "^0.1.0", diff --git a/dictation_server/src/common/notify/code.ts b/dictation_server/src/common/notify/code.ts deleted file mode 100644 index 032ae21..0000000 --- a/dictation_server/src/common/notify/code.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* -通知メッセージコード作成方針 -M+6桁(数字)で構成する。 -- 1~2桁目の値は種類(正常系、以上系...) -- 3~4桁目の値は関連機能(タスク、ユーザー、ファイル...) -- 5~6桁目の値は任意の重複しない値 -ex) -E00XXXX : 正常系メッセージ -E01XXXX : 以上系メッセージ -EXX00XX : 全般 -EXX01XX : タスク関連 -*/ -export const NotifyMessageCodes = ['M000101'] as const; diff --git a/dictation_server/src/common/notify/makeNotifyMessage.ts b/dictation_server/src/common/notify/makeNotifyMessage.ts deleted file mode 100644 index b5f2bf3..0000000 --- a/dictation_server/src/common/notify/makeNotifyMessage.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { notifyMessages } from './message'; -import { NotifyMessageCodeType } from './types/types'; - -export const makeNotifyMessage = (code: NotifyMessageCodeType): string => { - const msg = notifyMessages[code]; - return msg; -}; diff --git a/dictation_server/src/common/notify/message.ts b/dictation_server/src/common/notify/message.ts deleted file mode 100644 index 2b12876..0000000 --- a/dictation_server/src/common/notify/message.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { NotifyMessages } from './types/types'; - -// エラーコードとメッセージ対応表 -export const notifyMessages: NotifyMessages = { - M000101: 'You are assigned to Typist.', -}; diff --git a/dictation_server/src/common/notify/types/types.ts b/dictation_server/src/common/notify/types/types.ts index de01f01..1118464 100644 --- a/dictation_server/src/common/notify/types/types.ts +++ b/dictation_server/src/common/notify/types/types.ts @@ -1,7 +1,6 @@ -import { NotifyMessageCodes } from '../code'; - -export type NotifyMessageCodeType = (typeof NotifyMessageCodes)[number]; - -export type NotifyMessages = { - [P in NotifyMessageCodeType]: string; +export type NotificationBody = { + filename: string; + authorId: string; + priority: string; + uploadedAt: string; }; diff --git a/dictation_server/src/features/files/files.service.spec.ts b/dictation_server/src/features/files/files.service.spec.ts index 367abf3..34c6f00 100644 --- a/dictation_server/src/features/files/files.service.spec.ts +++ b/dictation_server/src/features/files/files.service.spec.ts @@ -32,7 +32,6 @@ import { import { createWorktype } from '../accounts/test/utility'; import { TasksRepositoryService } from '../../repositories/tasks/tasks.repository.service'; import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service'; -import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage'; import { getCheckoutPermissions, getTask } from '../tasks/test/utility'; import { DateWithZeroTime } from '../licenses/types/types'; import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants'; @@ -371,7 +370,12 @@ describe('タスク作成から自動ルーティング(DB使用)', () => { expect(NotificationHubService.notify).toHaveBeenCalledWith( makeContext('trackingId'), [`user_${typistUserId}`], - makeNotifyMessage('M000101'), + { + authorId: 'AUTHOR_ID', + filename: 'file', + priority: 'High', + uploadedAt: '2023-05-26T11:22:33.444', + }, ); // 作成したタスクを取得 const resultTask = await getTaskFromJobNumber(source, result.jobNumber); @@ -467,7 +471,12 @@ describe('タスク作成から自動ルーティング(DB使用)', () => { expect(NotificationHubService.notify).toHaveBeenCalledWith( makeContext('trackingId'), [`user_${typistUserId}`], - makeNotifyMessage('M000101'), + { + authorId: 'AUTHOR_ID', + filename: 'file', + priority: 'High', + uploadedAt: '2023-05-26T11:22:33.444', + }, ); // 作成したタスクを取得 const resultTask = await getTaskFromJobNumber(source, result.jobNumber); @@ -585,7 +594,12 @@ describe('タスク作成から自動ルーティング(DB使用)', () => { expect(NotificationHubService.notify).toHaveBeenCalledWith( makeContext('trackingId'), [`user_${typistUserId}`], - makeNotifyMessage('M000101'), + { + authorId: 'AUTHOR_ID', + filename: 'file', + priority: 'High', + uploadedAt: '2023-05-26T11:22:33.444', + }, ); // 作成したタスクを取得 const resultTask = await getTaskFromJobNumber(source, result.jobNumber); @@ -702,7 +716,12 @@ describe('タスク作成から自動ルーティング(DB使用)', () => { expect(NotificationHubService.notify).toHaveBeenCalledWith( makeContext('trackingId'), [`user_${typistUserId}`], - makeNotifyMessage('M000101'), + { + authorId: 'XXXXXXXXXX', + filename: 'file', + priority: 'High', + uploadedAt: '2023-05-26T11:22:33.444', + }, ); // 作成したタスクを取得 const resultTask = await getTaskFromJobNumber(source, result.jobNumber); @@ -716,7 +735,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => { // タスクのチェックアウト権限が想定通り(ワークフローで設定されている)のユーザーIDで作成されているか確認 expect(resultCheckoutPermission.length).toEqual(1); expect(resultCheckoutPermission[0].user_group_id).toEqual(userGroupId); - }, 1000000); + }); it('ワークフローが見つからない場合、タスク作成時に、自動ルーティングを行うことができない', async () => { if (!source) fail(); diff --git a/dictation_server/src/features/files/files.service.ts b/dictation_server/src/features/files/files.service.ts index 9453d2c..6e81ab1 100644 --- a/dictation_server/src/features/files/files.service.ts +++ b/dictation_server/src/features/files/files.service.ts @@ -30,7 +30,6 @@ import { } from '../../repositories/accounts/errors/types'; import { Task } from '../../repositories/tasks/entity/task.entity'; import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service'; -import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage'; import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service'; import { LicenseExpiredError, @@ -237,11 +236,12 @@ export class FilesService { this.logger.log(`tags: ${tags}`); // タグ対象に通知送信 - await this.notificationhubService.notify( - context, - tags, - makeNotifyMessage('M000101'), - ); + await this.notificationhubService.notify(context, tags, { + authorId: authorId, + filename: fileName.replace('.zip', ''), + priority: priority === '00' ? 'Normal' : 'High', + uploadedAt: uploadedDate, + }); // 追加したタスクのJOBナンバーを返却 return { jobNumber: task.job_number }; diff --git a/dictation_server/src/features/tasks/tasks.service.spec.ts b/dictation_server/src/features/tasks/tasks.service.spec.ts index d997da1..3eaef7d 100644 --- a/dictation_server/src/features/tasks/tasks.service.spec.ts +++ b/dictation_server/src/features/tasks/tasks.service.spec.ts @@ -35,7 +35,6 @@ import { } from '../workflows/test/utility'; import { createTemplateFile } from '../templates/test/utility'; import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service'; -import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage'; import { Roles } from '../../common/types/role'; describe('TasksService', () => { @@ -835,6 +834,9 @@ describe('changeCheckoutPermission', () => { await createCheckoutPermissions(source, taskId, typistUserId_1); await createCheckoutPermissions(source, taskId, undefined, userGroupId); const service = module.get(TasksService); + const NotificationHubService = module.get( + NotificationhubService, + ); await service.changeCheckoutPermission( makeContext('trackingId'), 1, @@ -850,6 +852,18 @@ describe('changeCheckoutPermission', () => { user_id: typistUserId_2, user_group_id: null, }); + const resultTask = await getTask(source, taskId); + // 通知処理が想定通りの引数で呼ばれているか確認 + expect(NotificationHubService.notify).toHaveBeenCalledWith( + makeContext('trackingId'), + [`user_${typistUserId_2}`], + { + authorId: 'MY_AUTHOR_ID', + filename: 'x', + priority: 'High', + uploadedAt: resultTask?.file?.uploaded_at.toISOString(), + }, + ); }); it('タスクのチェックアウト権限を変更できる。(グループ指定)', async () => { @@ -903,6 +917,9 @@ describe('changeCheckoutPermission', () => { await createCheckoutPermissions(source, taskId, typistUserId_1); await createCheckoutPermissions(source, taskId, undefined, userGroupId_1); const service = module.get(TasksService); + const NotificationHubService = module.get( + NotificationhubService, + ); await service.changeCheckoutPermission( makeContext('trackingId'), 1, @@ -918,6 +935,19 @@ describe('changeCheckoutPermission', () => { user_id: null, user_group_id: userGroupId_2, }); + + const resultTask = await getTask(source, taskId); + // 通知処理が想定通りの引数で呼ばれているか確認 + expect(NotificationHubService.notify).toHaveBeenCalledWith( + makeContext('trackingId'), + [`user_${typistUserId_2}`], + { + authorId: 'MY_AUTHOR_ID', + filename: 'x', + priority: 'High', + uploadedAt: resultTask?.file?.uploaded_at.toISOString(), + }, + ); }); it('タスクのチェックアウト権限を変更できる。(チェックアウト権限を外す)', async () => { @@ -2655,9 +2685,14 @@ describe('cancel', () => { expect(NotificationHubService.notify).toHaveBeenCalledWith( makeContext('trackingId'), [`user_${typistUserId}`], - makeNotifyMessage('M000101'), + { + authorId: 'AUTHOR_ID', + filename: 'x', + priority: 'High', + uploadedAt: resultTask?.file?.uploaded_at.toISOString(), + }, ); - }, 1000000); + }); it('API実行者のRoleがAdminの場合、自身が文字起こし実行中のタスクをキャンセルし、そのタスクの自動ルーティングを行う(API実行者のAuthorIDと音声ファイルに紐づくWorkType)', async () => { if (!source) fail(); @@ -2760,7 +2795,12 @@ describe('cancel', () => { expect(NotificationHubService.notify).toHaveBeenCalledWith( makeContext('trackingId'), [`user_${autoRoutingTypistUserId}`], - makeNotifyMessage('M000101'), + { + authorId: 'AUTHOR_ID', + filename: 'x', + priority: 'High', + uploadedAt: resultTask?.file?.uploaded_at.toISOString(), + }, ); }); it('API実行者のRoleがTypistの場合、自身が文字起こし実行中のタスクをキャンセルするが、一致するワークフローがない場合は自動ルーティングを行うことができない', async () => { diff --git a/dictation_server/src/features/tasks/tasks.service.ts b/dictation_server/src/features/tasks/tasks.service.ts index 164602c..ccd1f9d 100644 --- a/dictation_server/src/features/tasks/tasks.service.ts +++ b/dictation_server/src/features/tasks/tasks.service.ts @@ -31,7 +31,6 @@ import { Roles } from '../../common/types/role'; import { InvalidRoleError } from './errors/types'; import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service'; import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service'; -import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage'; import { Context } from '../../common/log'; import { User } from '../../repositories/users/entity/user.entity'; @@ -444,31 +443,13 @@ export class TasksService { user.author_id ?? undefined, ); - const groupMembers = - await this.userGroupsRepositoryService.getGroupMembersFromGroupIds( - typistGroupIds, - ); - - // 重複のない割り当て候補ユーザーID一覧を取得する - const distinctUserIds = [ - ...new Set([...typistIds, ...groupMembers.map((x) => x.user_id)]), - ]; - - // 割り当てられたユーザーがいない場合は通知不要 - if (distinctUserIds.length === 0) { - this.logger.log('No user assigned.'); - return; - } - - // タグを生成 - const tags = distinctUserIds.map((x) => `user_${x}`); - this.logger.log(`tags: ${tags}`); - - // タグ対象に通知送信 - await this.notificationhubService.notify( + // 通知を送信する + await this.sendNotify( context, - tags, - makeNotifyMessage('M000101'), + typistIds, + typistGroupIds, + audioFileId, + user.account_id, ); } catch (e) { // 処理の本筋はタスクキャンセルのため自動ルーティングに失敗してもエラーにしない @@ -600,35 +581,13 @@ export class TasksService { .flatMap((assignee) => assignee.typistUserId ? [assignee.typistUserId] : [], ); - - const groupMembers = - await this.userGroupsRepositoryService.getGroupMembersFromGroupIds( - assigneesGroupIds, - ); - - // 重複のない割り当て候補ユーザーID一覧を取得する - const distinctUserIds = [ - ...new Set([ - ...assigneesUserIds, - ...groupMembers.map((x) => x.user_id), - ]), - ]; - - // 割り当てられたユーザーがいない場合は通知不要 - if (distinctUserIds.length === 0) { - this.logger.log('No user assigned.'); - return; - } - - // タグを生成 - const tags = distinctUserIds.map((x) => `user_${x}`); - this.logger.log(`tags: ${tags}`); - - // タグ対象に通知送信 - await this.notificationhubService.notify( + // 通知を送信する + await this.sendNotify( context, - tags, - makeNotifyMessage('M000101'), + assigneesUserIds, + assigneesGroupIds, + audioFileId, + account_id, ); } catch (e) { this.logger.error(`error=${e}`); @@ -662,4 +621,51 @@ export class TasksService { ); } } + + // 通知を送信するプライベートメソッド + private async sendNotify( + context: Context, + typistUserIds: number[], + typistGroupIds: number[], + audioFileId: number, + accountId: number, + ): Promise { + const groupMembers = + await this.userGroupsRepositoryService.getGroupMembersFromGroupIds( + typistGroupIds, + ); + + // 重複のない割り当て候補ユーザーID一覧を取得する + const distinctUserIds = [ + ...new Set([...typistUserIds, ...groupMembers.map((x) => x.user_id)]), + ]; + + // 割り当てられたユーザーがいない場合は通知不要 + if (distinctUserIds.length === 0) { + this.logger.log('No user assigned.'); + return; + } + + // タグを生成 + const tags = distinctUserIds.map((x) => `user_${x}`); + this.logger.log(`tags: ${tags}`); + + // 通知内容に含む音声ファイル情報を取得 + const { file } = await this.taskRepository.getTaskAndAudioFile( + audioFileId, + accountId, + [TASK_STATUS.UPLOADED], + ); + if (!file) { + throw new Error('audio file not found'); + } + + // タグ対象に通知送信 + await this.notificationhubService.notify(context, tags, { + authorId: file.author_id, + filename: file.file_name.replace('.zip', ''), + priority: file.priority === '00' ? 'Normal' : 'High', + uploadedAt: file.uploaded_at.toISOString(), + }); + } } diff --git a/dictation_server/src/features/tasks/test/utility.ts b/dictation_server/src/features/tasks/test/utility.ts index 65e629b..705c0bd 100644 --- a/dictation_server/src/features/tasks/test/utility.ts +++ b/dictation_server/src/features/tasks/test/utility.ts @@ -207,6 +207,9 @@ export const getTask = async ( task_id: number, ): Promise => { const task = await datasource.getRepository(Task).findOne({ + relations: { + file: true, + }, where: { id: task_id, }, diff --git a/dictation_server/src/gateways/notificationhub/notificationhub.service.ts b/dictation_server/src/gateways/notificationhub/notificationhub.service.ts index 22038cc..a18025d 100644 --- a/dictation_server/src/gateways/notificationhub/notificationhub.service.ts +++ b/dictation_server/src/gateways/notificationhub/notificationhub.service.ts @@ -5,13 +5,15 @@ import { createAppleNotificationBody, createAppleNotification, createTagExpression, - createWindowsToastNotification, createWindowsInstallation, createAppleInstallation, + createWindowsRawNotification, } from '@azure/notification-hubs'; import { TAG_MAX_COUNT } from '../../constants'; import { PNS } from '../../constants'; import { Context } from '../../common/log'; +import { NotificationBody } from '../../common/notify/types/types'; + @Injectable() export class NotificationhubService { private readonly logger = new Logger(NotificationhubService.name); @@ -76,17 +78,22 @@ export class NotificationhubService { /** * 指定したタグのユーザーに通知を送信する + * @param context * @param tags - * @param message + * @param bodyContent * @returns notify */ async notify( context: Context, tags: string[], - message: string, + bodyContent: NotificationBody, ): Promise { this.logger.log( - `[IN] [${context.trackingId}] ${this.notify.name} | params: { tags: ${tags}, message: ${message} }`, + `[IN] [${context.trackingId}] ${ + this.notify.name + } | params: { tags: ${tags}, bodyContent: ${JSON.stringify( + bodyContent, + )} }`, ); try { @@ -99,8 +106,15 @@ export class NotificationhubService { // Windows try { - const body = `${message}`; - const notification = createWindowsToastNotification({ body }); + const body = { + wns: { + alert: '', + }, + newDictation: bodyContent, + }; + const notification = createWindowsRawNotification({ + body: JSON.stringify(body), + }); const result = await this.client.sendNotification(notification, { tagExpression, }); @@ -110,7 +124,12 @@ export class NotificationhubService { } // Apple try { - const body = createAppleNotificationBody({ aps: { alert: message } }); + const body = createAppleNotificationBody({ + aps: { + alert: '', + }, + newDictation: bodyContent, + }); const notification = createAppleNotification({ body }); const result = await this.client.sendNotification(notification, { tagExpression,