From b11f5164446d27f57f0ec40eee52d5e07288b470 Mon Sep 17 00:00:00 2001 From: impolitepanda Date: Wed, 15 Jan 2025 14:17:26 +0100 Subject: [PATCH] [frontend] fixes bulk operations not refreshing the datatables correctly (#2216) Co-authored-by: Guillaume Paris --- openbas-front/src/actions/Inject.js | 20 ++++++++++++- .../src/admin/components/common/Context.ts | 12 ++------ .../components/common/injects/Injects.tsx | 28 +++++++++++++++---- .../scenarios/scenario/ScenarioContext.ts | 13 ++++----- .../simulations/simulation/ExerciseContext.ts | 15 ++++------ .../simulation/injects/ExerciseInjects.tsx | 9 ++---- openbas-front/src/utils/Action.ts | 3 +- 7 files changed, 58 insertions(+), 42 deletions(-) diff --git a/openbas-front/src/actions/Inject.js b/openbas-front/src/actions/Inject.js index 625173e31d..cc4d896b03 100644 --- a/openbas-front/src/actions/Inject.js +++ b/openbas-front/src/actions/Inject.js @@ -1,4 +1,12 @@ -import { bulkDeleteReferential, delReferential, getReferential, postReferential, putReferential } from '../utils/Action'; +import { + bulkDeleteReferential, + delReferential, + getReferential, + postReferential, + putReferential, + simpleDelCall, + simplePutCall, +} from '../utils/Action'; import * as schema from './Schema'; // -- INJECTS -- @@ -13,11 +21,21 @@ export const bulkDeleteInjects = data => (dispatch) => { return bulkDeleteReferential(uri, 'injects', data)(dispatch); }; +export const bulkDeleteInjectsSimple = (data) => { + const uri = `/api/injects`; + return simpleDelCall(uri, data); +}; + export const bulkUpdateInject = data => (dispatch) => { const uri = `/api/injects`; return putReferential(schema.inject, uri, data)(dispatch); }; +export const bulkUpdateInjectSimple = (data) => { + const uri = `/api/injects`; + return simplePutCall(uri, data); +}; + // -- EXERCISES -- export const fetchExerciseInjects = exerciseId => (dispatch) => { diff --git a/openbas-front/src/admin/components/common/Context.ts b/openbas-front/src/admin/components/common/Context.ts index cf503d2d48..2b7b67b14d 100644 --- a/openbas-front/src/admin/components/common/Context.ts +++ b/openbas-front/src/admin/components/common/Context.ts @@ -89,10 +89,7 @@ export type TeamContextType = { export type InjectContextType = { searchInjects: (input: SearchPaginationInput) => Promise<{ data: Page }>; onAddInject: (inject: Inject) => Promise<{ result: string; entities: { injects: Record } }>; - onBulkUpdateInject: (param: InjectBulkUpdateInputs) => Promise<{ - result: string; - entities: { injects: Record }; - }>; + onBulkUpdateInject: (param: InjectBulkUpdateInputs) => Promise; onUpdateInject: (injectId: Inject['inject_id'], inject: Inject) => Promise<{ result: string; entities: { injects: Record } }>; onUpdateInjectTrigger?: (injectId: Inject['inject_id']) => Promise<{ result: string; entities: { injects: Record } }>; onUpdateInjectActivation: (injectId: Inject['inject_id'], injectEnabled: { inject_enabled: boolean }) => Promise<{ @@ -203,11 +200,8 @@ export const InjectContext = createContext({ onAddInject(_inject: Inject): Promise<{ result: string; entities: { injects: Record } }> { return Promise.resolve({ result: '', entities: { injects: {} } }); }, - onBulkUpdateInject(_param: InjectBulkUpdateInputs): Promise<{ - result: string; - entities: { injects: Record }; - }> { - return Promise.resolve({ result: '', entities: { injects: {} } }); + onBulkUpdateInject(_param: InjectBulkUpdateInputs): Promise { + return Promise.resolve([]); }, onUpdateInject(_injectId: Inject['inject_id'], _inject: Inject): Promise<{ result: string; entities: { injects: Record } }> { return Promise.resolve({ result: '', entities: { injects: {} } }); diff --git a/openbas-front/src/admin/components/common/injects/Injects.tsx b/openbas-front/src/admin/components/common/injects/Injects.tsx index 2020c1ed51..ca9bc8ae07 100644 --- a/openbas-front/src/admin/components/common/injects/Injects.tsx +++ b/openbas-front/src/admin/components/common/injects/Injects.tsx @@ -17,6 +17,7 @@ import { useQueryableWithLocalStorage } from '../../../../components/common/quer import { useFormatter } from '../../../../components/i18n'; import ItemBoolean from '../../../../components/ItemBoolean'; import ItemTags from '../../../../components/ItemTags'; +import Loader from '../../../../components/Loader'; import PlatformIcon from '../../../../components/PlatformIcon'; import type { Article, @@ -250,6 +251,8 @@ const Injects: FunctionComponent = ({ // Injects const [injects, setInjects] = useState([]); + // Bulk loading indcator for tests and delete + const [isBulkLoading, setIsBulkLoading] = useState(false); const [selectedInjectId, setSelectedInjectId] = useState(null); const [reloadInjectCount, setReloadInjectCount] = useState(0); @@ -264,13 +267,17 @@ const Injects: FunctionComponent = ({ const onUpdate = (result: { result: string; entities: { injects: Record } }) => { if (result.entities) { - const updated = result.entities.injects[result.result]; - setInjects(injects.map((i) => { - return (i.inject_id !== updated.inject_id ? i as InjectOutputType : (updated as InjectOutputType)); - })); + const updatedResults = result.entities.injects[result.result]; + setInjects(injects.map(i => i.inject_id !== updatedResults.inject_id ? i : updatedResults as InjectOutputType)); } }; + const onBulkUpdate = (updatedResults: Inject[]) => { + setInjects(injects.map((originalInject) => { + return updatedResults.find(updatedInject => updatedInject.inject_id === originalInject.inject_id) as unknown as InjectOutputType || originalInject; + })); + }; + const onDelete = (result: string) => { if (result) { setInjects(injects.filter(i => (i.inject_id !== result))); @@ -420,12 +427,13 @@ const Injects: FunctionComponent = ({ simulation_or_scenario_id: exerciseOrScenarioId, update_operations: operationsToPerform, }) - .then((result: { result: string; entities: { injects: Record } }) => { - onUpdate(result); + .then((result) => { + if (result) onBulkUpdate(result); }); }; const bulkDeleteInjects = () => { + setIsBulkLoading(true); const deleteIds = injectsToProcess.map((inject: InjectOutputType) => inject.inject_id); const ignoreIds = injectsToIgnore.map((inject: InjectOutputType) => inject.inject_id); injectContext.onBulkDeleteInjects({ @@ -440,10 +448,13 @@ const Injects: FunctionComponent = ({ const deletedIds = result.map(inject => inject.inject_id); setInjects(newNumbers !== 0 ? injects.filter(inject => !deletedIds.includes(inject.inject_id)) : []); queryableHelpers.paginationHelpers.handleChangeTotalElements(newNumbers); + }).finally(() => { + setIsBulkLoading(false); }); }; const massTestInjects = () => { + setIsBulkLoading(true); const testIds = injectsToProcess.map((inject: InjectOutputType) => inject.inject_id); const ignoreIds = injectsToIgnore.map((inject: InjectOutputType) => inject.inject_id); injectContext.bulkTestInjects({ @@ -461,6 +472,8 @@ const Injects: FunctionComponent = ({ itsDedicatedPage: {t('its dedicated page')}, })); } + }).finally(() => { + setIsBulkLoading(false); }); }; @@ -471,6 +484,9 @@ const Injects: FunctionComponent = ({ const atLeastOneValidInject = injects.some(inject => !inject.inject_injector_contract?.injector_contract_content_parsed); + if (isBulkLoading) { + return ; + } return ( <> { onAddInject(inject: Inject): Promise<{ result: string; entities: { injects: Record } }> { return dispatch(addInjectForScenario(scenario.scenario_id, inject)); }, - onBulkUpdateInject(param: InjectBulkUpdateInputs): Promise<{ - result: string; - entities: { injects: Record }; - }> { - return dispatch(bulkUpdateInject(param)); + onBulkUpdateInject(param: InjectBulkUpdateInputs): Promise { + return bulkUpdateInjectSimple(param).then((result: { data: Inject[] }) => result?.data); }, onUpdateInject(injectId: Inject['inject_id'], inject: Inject): Promise<{ result: string; entities: { injects: Record } }> { return dispatch(updateInjectForScenario(scenario.scenario_id, injectId, inject)); @@ -68,7 +65,7 @@ const injectContextForScenario = (scenario: Scenario) => { return dryImportXlsForScenario(scenario.scenario_id, importId, input).then(result => result.data); }, onBulkDeleteInjects(param: InjectBulkProcessingInput): Promise { - return dispatch(bulkDeleteInjects(param)); + return bulkDeleteInjectsSimple(param).then((result: { data: Inject[] }) => result?.data); }, bulkTestInjects(param: InjectBulkProcessingInput): Promise<{ uri: string; data: InjectTestStatus[] }> { return bulkTestInjects(param).then(result => ({ diff --git a/openbas-front/src/admin/components/simulations/simulation/ExerciseContext.ts b/openbas-front/src/admin/components/simulations/simulation/ExerciseContext.ts index e303aa07e2..e7978a40a3 100644 --- a/openbas-front/src/admin/components/simulations/simulation/ExerciseContext.ts +++ b/openbas-front/src/admin/components/simulations/simulation/ExerciseContext.ts @@ -2,8 +2,8 @@ import { fetchExercise, fetchExerciseTeams } from '../../../../actions/Exercise' import { dryImportXlsForExercise, importXlsForExercise } from '../../../../actions/exercises/exercise-action'; import { addInjectForExercise, - bulkDeleteInjects, - bulkUpdateInject, + bulkDeleteInjectsSimple, + bulkUpdateInjectSimple, deleteInjectForExercise, fetchExerciseInjects, injectDone, @@ -36,12 +36,8 @@ const injectContextForExercise = (exercise: Exercise) => { onAddInject(inject: Inject): Promise<{ result: string; entities: { injects: Record } }> { return dispatch(addInjectForExercise(exercise.exercise_id, inject)); }, - onBulkUpdateInject(param: InjectBulkUpdateInputs): Promise<{ - result: string; - entities: { injects: Record }; - }> { - // exercise.exercise_id - return dispatch(bulkUpdateInject(param)); + onBulkUpdateInject(param: InjectBulkUpdateInputs): Promise { + return bulkUpdateInjectSimple(param).then((result: { data: Inject[] }) => result?.data); }, onUpdateInject(injectId: Inject['inject_id'], inject: Inject): Promise<{ result: string; entities: { injects: Record } }> { return dispatch(updateInjectForExercise(exercise.exercise_id, injectId, inject)); @@ -73,8 +69,7 @@ const injectContextForExercise = (exercise: Exercise) => { return dryImportXlsForExercise(exercise.exercise_id, importId, input).then(result => result.data); }, onBulkDeleteInjects(param: InjectBulkProcessingInput): Promise { - // exercise.exercise_id - return dispatch(bulkDeleteInjects(param)); + return bulkDeleteInjectsSimple(param).then((result: { data: Inject[] }) => result?.data); }, bulkTestInjects(param: InjectBulkProcessingInput): Promise<{ uri: string; data: InjectTestStatus[] }> { return bulkTestInjects(param).then(result => ({ diff --git a/openbas-front/src/admin/components/simulations/simulation/injects/ExerciseInjects.tsx b/openbas-front/src/admin/components/simulations/simulation/injects/ExerciseInjects.tsx index 7ffa575441..b979df9b24 100644 --- a/openbas-front/src/admin/components/simulations/simulation/injects/ExerciseInjects.tsx +++ b/openbas-front/src/admin/components/simulations/simulation/injects/ExerciseInjects.tsx @@ -9,8 +9,6 @@ import type { ArticlesHelper } from '../../../../../actions/channels/article-hel import { fetchExerciseInjectExpectations, fetchExerciseTeams } from '../../../../../actions/Exercise'; import type { ExercisesHelper } from '../../../../../actions/exercises/exercise-helper'; import type { ChallengeHelper } from '../../../../../actions/helper'; -import { fetchExerciseInjectsSimple } from '../../../../../actions/injects/inject-action'; -import type { InjectHelper } from '../../../../../actions/injects/inject-helper'; import { fetchVariablesForExercise } from '../../../../../actions/variables/variable-actions'; import type { VariablesHelper } from '../../../../../actions/variables/variable-helper'; import { useFormatter } from '../../../../../components/i18n'; @@ -56,10 +54,9 @@ const ExerciseInjects: FunctionComponent = () => { setViewMode(mode); }; - const { injects, exercise, teams, articles, variables } = useHelper( - (helper: InjectHelper & ExercisesHelper & ArticlesHelper & ChallengeHelper & VariablesHelper) => { + const { exercise, teams, articles, variables } = useHelper( + (helper: ExercisesHelper & ArticlesHelper & ChallengeHelper & VariablesHelper) => { return { - injects: helper.getExerciseInjects(exerciseId), exercise: helper.getExercise(exerciseId), teams: helper.getExerciseTeams(exerciseId), articles: helper.getExerciseArticles(exerciseId), @@ -68,7 +65,6 @@ const ExerciseInjects: FunctionComponent = () => { }, ); useDataLoader(() => { - dispatch(fetchExerciseInjectsSimple(exerciseId)); dispatch(fetchExerciseTeams(exerciseId)); dispatch(fetchExerciseArticles(exerciseId)); dispatch(fetchVariablesForExercise(exerciseId)); @@ -87,7 +83,6 @@ const ExerciseInjects: FunctionComponent = () => { simpleApi.delete(buildUri(uri)) +// eslint-disable-next-line max-len +export const simpleDelCall = (uri: string, data?: unknown, defaultNotifyErrorBehavior: boolean = true, defaultSuccessBehavior: boolean = true) => simpleApi.delete(buildUri(uri), data ? { data: data } : undefined) .then((response) => { if (defaultSuccessBehavior) { notifySuccess('The element has been successfully deleted.');