From bbbd3e757b54f5768ace6117433205b9afe9099d Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Tue, 13 Feb 2024 02:32:17 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20743:=20=E3=83=86=E3=83=B3?= =?UTF-8?q?=E3=83=97=E3=83=AC=E3=83=BC=E3=83=88=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E5=89=8A=E9=99=A4=E7=94=BB=E9=9D=A2=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task3600: テンプレートファイル削除画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3600) - テンプレートファイル削除の画面を実装しました。 ## レビューポイント - エラー処理は適切でしょうか? ## UIの変更 - [Task3600](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3600?csf=1&web=1&e=E25Kf7) ## 動作確認状況 - ローカルで確認 --- dictation_client/src/api/api.ts | 73 ++++++++++++++++++ dictation_client/src/common/errors/code.ts | 3 + .../features/workflow/template/operations.ts | 75 +++++++++++++++++++ .../workflow/template/templateSlice.ts | 15 +++- .../src/pages/TemplateFilePage/index.tsx | 27 ++++++- dictation_client/src/translation/de.json | 4 + dictation_client/src/translation/en.json | 4 + dictation_client/src/translation/es.json | 4 + dictation_client/src/translation/fr.json | 4 + 9 files changed, 204 insertions(+), 5 deletions(-) diff --git a/dictation_client/src/api/api.ts b/dictation_client/src/api/api.ts index d510afb..0b3aacf 100644 --- a/dictation_client/src/api/api.ts +++ b/dictation_client/src/api/api.ts @@ -6730,6 +6730,44 @@ export class TasksApi extends BaseAPI { */ export const TemplatesApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * ログインしているユーザーのアカウント配下でIDで指定されたテンプレートファイルを削除します + * @summary + * @param {number} templateFileId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteTemplateFile: async (templateFileId: number, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'templateFileId' is not null or undefined + assertParamExists('deleteTemplateFile', 'templateFileId', templateFileId) + const localVarPath = `/templates/{templateFileId}/delete` + .replace(`{${"templateFileId"}}`, encodeURIComponent(String(templateFileId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * アカウント内のテンプレートファイルの一覧を取得します * @summary @@ -6774,6 +6812,19 @@ export const TemplatesApiAxiosParamCreator = function (configuration?: Configura export const TemplatesApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = TemplatesApiAxiosParamCreator(configuration) return { + /** + * ログインしているユーザーのアカウント配下でIDで指定されたテンプレートファイルを削除します + * @summary + * @param {number} templateFileId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async deleteTemplateFile(templateFileId: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteTemplateFile(templateFileId, options); + const index = configuration?.serverIndex ?? 0; + const operationBasePath = operationServerMap['TemplatesApi.deleteTemplateFile']?.[index]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath); + }, /** * アカウント内のテンプレートファイルの一覧を取得します * @summary @@ -6796,6 +6847,16 @@ export const TemplatesApiFp = function(configuration?: Configuration) { export const TemplatesApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = TemplatesApiFp(configuration) return { + /** + * ログインしているユーザーのアカウント配下でIDで指定されたテンプレートファイルを削除します + * @summary + * @param {number} templateFileId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + deleteTemplateFile(templateFileId: number, options?: any): AxiosPromise { + return localVarFp.deleteTemplateFile(templateFileId, options).then((request) => request(axios, basePath)); + }, /** * アカウント内のテンプレートファイルの一覧を取得します * @summary @@ -6815,6 +6876,18 @@ export const TemplatesApiFactory = function (configuration?: Configuration, base * @extends {BaseAPI} */ export class TemplatesApi extends BaseAPI { + /** + * ログインしているユーザーのアカウント配下でIDで指定されたテンプレートファイルを削除します + * @summary + * @param {number} templateFileId + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TemplatesApi + */ + public deleteTemplateFile(templateFileId: number, options?: AxiosRequestConfig) { + return TemplatesApiFp(this.configuration).deleteTemplateFile(templateFileId, options).then((request) => request(this.axios, this.basePath)); + } + /** * アカウント内のテンプレートファイルの一覧を取得します * @summary diff --git a/dictation_client/src/common/errors/code.ts b/dictation_client/src/common/errors/code.ts index 5aa1497..25366a9 100644 --- a/dictation_client/src/common/errors/code.ts +++ b/dictation_client/src/common/errors/code.ts @@ -73,4 +73,7 @@ export const errorCodes = [ "E015001", // タイピストグループ削除済みエラー "E015002", // タイピストグループがワークフローに紐づいているエラー "E015003", // タイピストグループがルーティングされているエラー + "E016001", // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルがすでに削除済みだった) + "E016002", // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルがWorkflowに指定されていた) + "E016003", // テンプレートファイル削除エラー(削除しようとしたテンプレートファイルが未完了のタスクに紐づいていた) ] as const; diff --git a/dictation_client/src/features/workflow/template/operations.ts b/dictation_client/src/features/workflow/template/operations.ts index 9a15ac2..ae464f3 100644 --- a/dictation_client/src/features/workflow/template/operations.ts +++ b/dictation_client/src/features/workflow/template/operations.ts @@ -115,3 +115,78 @@ export const uploadTemplateAsync = createAsyncThunk< return thunkApi.rejectWithValue({ error }); } }); + +export const deleteTemplateAsync = createAsyncThunk< + { + /* Empty Object */ + }, + { templateFileId: number }, + { + // rejectした時の返却値の型 + rejectValue: { + error: ErrorObject; + }; + } +>("workflow/deleteTemplateAsync", async (args, thunkApi) => { + const { templateFileId } = args; + // apiのConfigurationを取得する + const { getState } = thunkApi; + const state = getState() as RootState; + const { configuration } = state.auth; + const accessToken = getAccessToken(state.auth); + const config = new Configuration(configuration); + const templateApi = new TemplatesApi(config); + + try { + // ファイルを削除する + await templateApi.deleteTemplateFile(templateFileId, { + headers: { authorization: `Bearer ${accessToken}` }, + }); + + thunkApi.dispatch( + openSnackbar({ + level: "info", + message: getTranslationID("common.message.success"), + }) + ); + + return {}; + } catch (e) { + // e ⇒ errorObjectに変換" + const error = createErrorObject(e); + + if (error.code === "E016001") { + // テンプレートファイルが削除済みの場合は成功扱いとする + thunkApi.dispatch( + openSnackbar({ + level: "info", + message: getTranslationID("common.message.success"), + }) + ); + return {}; + } + + let message = getTranslationID("common.message.internalServerError"); + + // テンプレートファイルがルーティングルールに紐づく場合はエラー + if (error.code === "E016002") { + message = getTranslationID( + "templateFilePage.message.deleteFailedWorkflowAssigned" + ); + } + // テンプレートファイルが未完了のタスクに紐づく場合はエラー + if (error.code === "E016003") { + message = getTranslationID( + "templateFilePage.message.deleteFailedTaskAssigned" + ); + } + + thunkApi.dispatch( + openSnackbar({ + level: "error", + message, + }) + ); + return thunkApi.rejectWithValue({ error }); + } +}); diff --git a/dictation_client/src/features/workflow/template/templateSlice.ts b/dictation_client/src/features/workflow/template/templateSlice.ts index bbd4001..d567fe7 100644 --- a/dictation_client/src/features/workflow/template/templateSlice.ts +++ b/dictation_client/src/features/workflow/template/templateSlice.ts @@ -1,6 +1,10 @@ import { PayloadAction, createSlice } from "@reduxjs/toolkit"; import { TemplateState } from "./state"; -import { listTemplateAsync, uploadTemplateAsync } from "./operations"; +import { + deleteTemplateAsync, + listTemplateAsync, + uploadTemplateAsync, +} from "./operations"; const initialState: TemplateState = { apps: { @@ -45,6 +49,15 @@ export const templateSlice = createSlice({ builder.addCase(uploadTemplateAsync.rejected, (state) => { state.apps.isUploading = false; }); + builder.addCase(deleteTemplateAsync.pending, (state) => { + state.apps.isLoading = true; + }); + builder.addCase(deleteTemplateAsync.fulfilled, (state) => { + state.apps.isLoading = false; + }); + builder.addCase(deleteTemplateAsync.rejected, (state) => { + state.apps.isLoading = false; + }); }, }); diff --git a/dictation_client/src/pages/TemplateFilePage/index.tsx b/dictation_client/src/pages/TemplateFilePage/index.tsx index 59ef540..f50dd0f 100644 --- a/dictation_client/src/pages/TemplateFilePage/index.tsx +++ b/dictation_client/src/pages/TemplateFilePage/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { AppDispatch } from "app/store"; import Header from "components/header"; @@ -13,6 +13,7 @@ import { selectTemplates, listTemplateAsync, selectIsLoading, + deleteTemplateAsync, } from "features/workflow/template"; import { selectDelegationAccessToken } from "features/auth/selectors"; import { DelegationBar } from "components/delegate"; @@ -35,6 +36,23 @@ export const TemplateFilePage: React.FC = () => { dispatch(listTemplateAsync()); }, [dispatch]); + const onDeleteTemplate = useCallback( + async (templateFileId: number) => { + if ( + /* eslint-disable-next-line no-alert */ + !window.confirm(t(getTranslationID("common.message.dialogConfirm"))) + ) { + return; + } + + const { meta } = await dispatch(deleteTemplateAsync({ templateFileId })); + if (meta.requestStatus === "fulfilled") { + dispatch(listTemplateAsync()); + } + }, + [dispatch, t] + ); + return ( <> {isShowAddPopup && ( @@ -101,16 +119,17 @@ export const TemplateFilePage: React.FC = () => { {template.name} diff --git a/dictation_client/src/translation/de.json b/dictation_client/src/translation/de.json index 17289e4..01f661c 100644 --- a/dictation_client/src/translation/de.json +++ b/dictation_client/src/translation/de.json @@ -475,6 +475,10 @@ "fileSizeTerms": "Die maximale Dateigröße, die gespeichert werden kann, beträgt 5 MB.", "fileSizeError": "Die ausgewählte Dateigröße ist zu groß. Bitte wählen Sie eine Datei mit einer Größe von 5 MB oder weniger aus.", "fileEmptyError": "Dateiauswahl ist erforderlich. Bitte wählen Sie eine Datei aus." + }, + "message": { + "deleteFailedWorkflowAssigned": "(de)テンプレートファイルの削除に失敗しました。Workflow画面でルーティングルールから対象テンプレートファイルを外してください。", + "deleteFailedTaskAssigned": "(de)テンプレートファイルの削除に失敗しました。Dictation画面で対象テンプレートファイルが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。" } }, "partnerPage": { diff --git a/dictation_client/src/translation/en.json b/dictation_client/src/translation/en.json index cdd73e7..c58bd89 100644 --- a/dictation_client/src/translation/en.json +++ b/dictation_client/src/translation/en.json @@ -475,6 +475,10 @@ "fileSizeTerms": "The maximum file size that can be saved is 5MB.", "fileSizeError": "The selected file size is too large. Please select a file that is 5MB or less in size.", "fileEmptyError": "File selection is required. Please select a file." + }, + "message": { + "deleteFailedWorkflowAssigned": "テンプレートファイルの削除に失敗しました。Workflow画面でルーティングルールから対象テンプレートファイルを外してください。", + "deleteFailedTaskAssigned": "テンプレートファイルの削除に失敗しました。Dictation画面で対象テンプレートファイルが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。" } }, "partnerPage": { diff --git a/dictation_client/src/translation/es.json b/dictation_client/src/translation/es.json index 2256420..7a743a5 100644 --- a/dictation_client/src/translation/es.json +++ b/dictation_client/src/translation/es.json @@ -475,6 +475,10 @@ "fileSizeTerms": "El tamaño máximo de archivo que se puede guardar es de 5 MB.", "fileSizeError": "El tamaño del archivo seleccionado es demasiado grande. Seleccione un archivo que tenga un tamaño de 5 MB o menos.", "fileEmptyError": "Se requiere selección de archivos. Por favor seleccione un archivo." + }, + "message": { + "deleteFailedWorkflowAssigned": "(es)テンプレートファイルの削除に失敗しました。Workflow画面でルーティングルールから対象テンプレートファイルを外してください。", + "deleteFailedTaskAssigned": "(es)テンプレートファイルの削除に失敗しました。Dictation画面で対象テンプレートファイルが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。" } }, "partnerPage": { diff --git a/dictation_client/src/translation/fr.json b/dictation_client/src/translation/fr.json index 403e693..7a0e0b1 100644 --- a/dictation_client/src/translation/fr.json +++ b/dictation_client/src/translation/fr.json @@ -475,6 +475,10 @@ "fileSizeTerms": "La taille maximale du fichier pouvant être enregistré est de 5 Mo.", "fileSizeError": "La taille du fichier sélectionné est trop grande. Veuillez sélectionner un fichier d'une taille maximale de 5 Mo.", "fileEmptyError": "La sélection de fichiers est requise. Veuillez sélectionner un fichier." + }, + "message": { + "deleteFailedWorkflowAssigned": "(fr)テンプレートファイルの削除に失敗しました。Workflow画面でルーティングルールから対象テンプレートファイルを外してください。", + "deleteFailedTaskAssigned": "(fr)テンプレートファイルの削除に失敗しました。Dictation画面で対象テンプレートファイルが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。" } }, "partnerPage": {