Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug/12035 not possible to delete a data model in studio #12115

Merged
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3fd0f4d
fixed remove datamodell
JamalAlabdullah Jan 23, 2024
8b33108
Fix metadata mocks
mlqn Jan 23, 2024
9aa44ef
updated test and useOnUnmount function
JamalAlabdullah Jan 23, 2024
2d41867
removed snapshots in ProfileMenu
JamalAlabdullah Jan 23, 2024
68347b4
Merge remote-tracking branch 'origin/main' into bug/12035-not-possibl…
JamalAlabdullah Jan 23, 2024
bf7af35
Merge remote-tracking branch 'origin/main' into bug/12035-not-possibl…
JamalAlabdullah Jan 23, 2024
c4267ac
Merge remote-tracking branch 'origin/main' into bug/12035-not-possibl…
JamalAlabdullah Jan 23, 2024
8392f1b
test profile menu
JamalAlabdullah Jan 23, 2024
95470fd
Merge branch 'main' into bug/12035-not-possible-to-delete-a-data-mode…
JamalAlabdullah Feb 4, 2024
6e00896
Merge remote-tracking branch 'origin/main' into bug/12035-not-possibl…
JamalAlabdullah Feb 4, 2024
5e60266
Merge branch 'bug/12035-not-possible-to-delete-a-data-model-in-studio…
JamalAlabdullah Feb 4, 2024
661038e
fixed Typechecking error
JamalAlabdullah Feb 4, 2024
923c1de
fixed deletion of old datamodel
JamalAlabdullah Feb 4, 2024
356a8eb
Merge remote-tracking branch 'origin/main' into bug/12035-not-possibl…
JamalAlabdullah Feb 5, 2024
7386105
added test
JamalAlabdullah Feb 5, 2024
6c1df48
updated test
JamalAlabdullah Feb 5, 2024
0a11876
updated test
JamalAlabdullah Feb 5, 2024
f1ccea5
Merge remote-tracking branch 'origin/main' into bug/12035-not-possibl…
JamalAlabdullah Feb 5, 2024
20b46be
updated
JamalAlabdullah Feb 5, 2024
46cf3ad
updated test
JamalAlabdullah Feb 5, 2024
0233d0a
Merge remote-tracking branch 'origin/main' into bug/12035-not-possibl…
JamalAlabdullah Feb 6, 2024
080d7d6
fixed comments
JamalAlabdullah Feb 7, 2024
de87fe6
Merge remote-tracking branch 'origin/main' into bug/12035-not-possibl…
JamalAlabdullah Feb 7, 2024
6b1cb18
Update frontend/app-development/features/dataModelling/SchemaEditorWi…
JamalAlabdullah Feb 7, 2024
eddd802
Merge remote-tracking branch 'origin/main' into bug/12035-not-possibl…
JamalAlabdullah Feb 9, 2024
b493dc0
Merge branch 'bug/12035-not-possible-to-delete-a-data-model-in-studio…
JamalAlabdullah Feb 9, 2024
7abd17c
removed zip filer
JamalAlabdullah Feb 9, 2024
9ac10b4
add test-result til gitignore
JamalAlabdullah Feb 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
import { act, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react';
import { textMock } from '../../../../testing/mocks/i18nMock';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { datamodelNameMock } from 'app-shared/mocks/datamodelMetadataMocks';
import { datamodelNameMock, jsonMetadataMock } from 'app-shared/mocks/datamodelMetadataMocks';
import userEvent from '@testing-library/user-event';
import { dataMock } from '@altinn/schema-editor/mockData';
import { AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS } from 'app-shared/constants';
Expand All @@ -17,7 +17,7 @@ import { createApiErrorMock } from 'app-shared/mocks/apiErrorMock';
const user = userEvent.setup();

// Test data:
const modelPath = datamodelNameMock;
const modelPath = jsonMetadataMock.repositoryRelativeUrl;
const defaultProps: SelectedSchemaEditorProps = {
modelPath,
};
Expand All @@ -36,6 +36,10 @@ jest.mock('@altinn/schema-editor/SchemaEditorApp', () => ({
}));
jest.useFakeTimers({ advanceTimers: true });

jest.mock('../../../utils/metadataUtils', () => ({
mergeJsonAndXsdData: jest.fn().mockImplementation(() => [jsonMetadataMock]),
}));
TomasEng marked this conversation as resolved.
Show resolved Hide resolved

describe('SelectedSchemaEditor', () => {
it('Displays loading spinner while loading', () => {
render();
Expand Down Expand Up @@ -72,6 +76,7 @@ describe('SelectedSchemaEditor', () => {
const getDatamodel = jest.fn().mockImplementation(() => Promise.resolve(dataMock));

render({ getDatamodel, saveDatamodel });

await waitForElementToBeRemoved(() => screen.queryByTitle(textMock('general.loading')));

const button = screen.getByTestId(saveButtonTestId);
Expand All @@ -83,30 +88,29 @@ describe('SelectedSchemaEditor', () => {
expect(saveDatamodel).toHaveBeenCalledWith(org, app, modelPath, dataMock);
});

it('Autosaves when changing between models that are not present in the cache', async () => {
test('Autosaves when changing between models that are not present in the cache', async () => {
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
const saveDatamodel = jest.fn();
const queryClient = createQueryClientMock();
queryClient.setQueryData([QueryKey.JsonSchema, org, app, modelPath], dataMock);
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
const getDatamodel = jest.fn().mockImplementation(() => Promise.resolve(dataMock));
const {
renderResult: { rerender },
} = render({ getDatamodel, saveDatamodel });
await waitForElementToBeRemoved(() => screen.queryByTitle(textMock('general.loading')));
expect(saveDatamodel).not.toHaveBeenCalled();

const updatedProps = {
...defaultProps,
modelPath: 'newModel',
};
const updatedProps = { ...defaultProps, modelPath: 'newModel' };
rerender(<SelectedSchemaEditor {...updatedProps} />);
jest.advanceTimersByTime(AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS);
await waitFor(() => expect(saveDatamodel).toHaveBeenCalledTimes(1));
expect(saveDatamodel).toHaveBeenCalledWith(org, app, datamodelNameMock, dataMock);
expect(saveDatamodel).toHaveBeenCalledWith(org, app, modelPath, dataMock);
});

it('Autosaves when changing between models that are already present in the cache', async () => {
const saveDatamodel = jest.fn();
const queryClient = createQueryClientMock();
const newModelPath = 'newModel';
queryClient.setQueryData([QueryKey.JsonSchema, org, app, datamodelNameMock], dataMock);
queryClient.setQueryData([QueryKey.JsonSchema, org, app, modelPath], dataMock);
queryClient.setQueryData([QueryKey.JsonSchema, org, app, newModelPath], dataMock);
const {
renderResult: { rerender },
Expand All @@ -120,7 +124,23 @@ describe('SelectedSchemaEditor', () => {
rerender(<SelectedSchemaEditor {...updatedProps} />);
jest.advanceTimersByTime(AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS);
await waitFor(() => expect(saveDatamodel).toHaveBeenCalledTimes(1));
expect(saveDatamodel).toHaveBeenCalledWith(org, app, datamodelNameMock, dataMock);
expect(saveDatamodel).toHaveBeenCalledWith(org, app, modelPath, dataMock);
});

test('Does not save the datamodel when unmounting if the datamodel has been deleted', async () => {
const saveDatamodel = jest.fn();
const queryClient = createQueryClientMock();
const datamodelMetadataList = jest.fn().mockImplementation(() => [
{
...jsonMetadataMock,
repositoryRelativeUrl: modelPath,
},
]);
queryClient.setQueryData([QueryKey.JsonSchema, org, app, datamodelNameMock], dataMock);
queryClient.setQueryData([QueryKey.DatamodelsJson, org, app], datamodelMetadataList);
render({ saveDatamodel });
queryClient.setQueryData([QueryKey.DatamodelsJson, org, app], []);
expect(saveDatamodel).not.toHaveBeenCalled();
});
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ import { useTranslation } from 'react-i18next';
import { AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS } from 'app-shared/constants';
import type { JsonSchema } from 'app-shared/types/JsonSchema';
import { useOnUnmount } from 'app-shared/hooks/useOnUnmount';
import type { DatamodelMetadataJson, DatamodelMetadataXsd } from 'app-shared/types/DatamodelMetadata';
import { useQueryClient } from '@tanstack/react-query';
import { QueryKey } from 'app-shared/types/QueryKey';
import { useStudioUrlParams } from 'app-shared/hooks/useStudioUrlParams';
import { mergeJsonAndXsdData } from 'app-development/utils/metadataUtils';
import { extractFilename, removeSchemaExtension } from 'app-shared/utils/filenameUtils';


export interface SelectedSchemaEditorProps {
modelPath: string;
}
Expand Down Expand Up @@ -46,7 +52,9 @@ interface SchemaEditorWithDebounceProps {
}

const SchemaEditorWithDebounce = ({ jsonSchema, modelPath }: SchemaEditorWithDebounceProps) => {
const { org, app } = useStudioUrlParams();
const { mutate } = useSchemaMutation();
const queryClient = useQueryClient();
const [model, setModel] = useState<JsonSchema>(jsonSchema);
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const updatedModel = useRef<JsonSchema>(jsonSchema);
Expand All @@ -70,7 +78,21 @@ const SchemaEditorWithDebounce = ({ jsonSchema, modelPath }: SchemaEditorWithDeb

useOnUnmount(() => {
clearTimeout(saveTimeoutRef.current);
saveFunction();
const jsonModels: DatamodelMetadataJson[] = queryClient.getQueryData([
QueryKey.DatamodelsJson,
org,
app,
]);
const xsdModels: DatamodelMetadataXsd[] = queryClient.getQueryData([
QueryKey.DatamodelsXsd,
org,
app,
]);
const metadataList = mergeJsonAndXsdData(jsonModels, xsdModels);
const datamodelExists = metadataList.some(
(datamodel) => datamodel.repositoryRelativeUrl === modelPath,
);
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
if (datamodelExists) saveFunction();
});

return (
Expand Down
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { renderHookWithMockStore } from '../../test/mocks';
import { removeDatamodelFromList, useDeleteDatamodelMutation } from './useDeleteDatamodelMutation';
import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
import type { QueryClient } from '@tanstack/react-query';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { jsonSchemaMock } from '../../test/jsonSchemaMock';
import { waitFor } from '@testing-library/react';
import { QueryKey } from 'app-shared/types/QueryKey';
import { DatamodelMetadata } from 'app-shared/types/DatamodelMetadata';
Fixed Show fixed Hide fixed

const DatamodelMetadata = jest.fn();
const modelPath = 'modelPath';
const org = 'org';
const app = 'app';
const initialData: DatamodelMetadata[] = [
{
description: null,
directory: 'directory',
fileName: 'fileName',
filePath: 'filePath',
fileStatus: 'fileStatus',
fileType: '.json',
lastChanged: 'lastChanged',
repositoryRelativeUrl: 'repositoryRelativeUrl',
select: true,
},
];

describe('useDeleteDatamodelMutation', () => {
beforeEach(jest.clearAllMocks);

it('Returns correct state with the correct parameters', async () => {
const queryClient = createQueryClientMock();
const deleteDatamodel = jest.fn();
const {
renderHookResult: { result },
} = render({ deleteDatamodel });
result.current.mutate({ modelPath, model: jsonSchemaMock });
await waitFor(() => result.current.isPending);
expect(
queryClient.setQueryData(
[QueryKey.DatamodelsJson, org, app],
(oldData: DatamodelMetadata[] = initialData) => removeDatamodelFromList(oldData, modelPath),
),
).toEqual(initialData);
expect(
queryClient.setQueryData(
[QueryKey.DatamodelsXsd, org, app],
(oldData: DatamodelMetadata[] = initialData) => removeDatamodelFromList(oldData, modelPath),
),
).toEqual(initialData);
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
await waitFor(() => result.current.isSuccess);
expect(removeDatamodelFromList).toHaveBeenCalled;
});

it('Calls onSuccess correctly', async () => {
const queryClient = createQueryClientMock();
const deleteDatamodel = jest.fn();
const {
renderHookResult: { result },
} = render({ deleteDatamodel });
result.current.mutate({ modelPath, model: jsonSchemaMock });
await waitFor(() => result.current.isSuccess);
queryClient.setQueryData([QueryKey.DatamodelsJson, org, app], initialData);
queryClient.setQueryData([QueryKey.DatamodelsXsd, org, app], initialData);
result.current.mutate({ modelPath, model: jsonSchemaMock });
await waitFor(() => result.current.isSuccess);
expect(
queryClient.setQueryData(
[QueryKey.DatamodelsJson, org, app],
(oldData: DatamodelMetadata[] = initialData) => removeDatamodelFromList(oldData, modelPath),
),
).toEqual(initialData);
expect(
queryClient.setQueryData(
[QueryKey.DatamodelsXsd, org, app],
(oldData: DatamodelMetadata[] = initialData) => removeDatamodelFromList(oldData, modelPath),
),
).toEqual(initialData);
expect(
queryClient.removeQueries({ queryKey: [QueryKey.JsonSchema, org, app, modelPath] }),
).toEqual(undefined);
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
});
});

const render = (
queries: Partial<ServicesContextProps> = {},
queryClient: QueryClient = createQueryClientMock(),
) => renderHookWithMockStore({}, queries, queryClient)(() => useDeleteDatamodelMutation());
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,44 @@ import { useServicesContext } from 'app-shared/contexts/ServicesContext';
import { QueryKey } from 'app-shared/types/QueryKey';
import { useStudioUrlParams } from 'app-shared/hooks/useStudioUrlParams';
import { isXsdFile } from 'app-shared/utils/filenameUtils';
import type { DatamodelMetadata } from 'app-shared/types/DatamodelMetadata';

export const useDeleteDatamodelMutation = () => {
const { deleteDatamodel } = useServicesContext();
const { org, app } = useStudioUrlParams();
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (modelPath: string) => {
await deleteDatamodel(org, app, modelPath);
queryClient.setQueryData(
[QueryKey.DatamodelsJson, org, app],
(oldData: DatamodelMetadata[]) => removeDatamodelFromList(oldData, modelPath),
);
queryClient.setQueryData([QueryKey.DatamodelsXsd, org, app], (oldData: DatamodelMetadata[]) =>
removeDatamodelFromList(oldData, modelPath),
);
const respectiveFileNameInXsdOrJson = isXsdFile(modelPath)
? modelPath.replace('.xsd', '.schema.json')
: modelPath.replace('.schema.json', '.xsd');
queryClient.setQueryData([QueryKey.JsonSchema, org, app, modelPath], undefined);
queryClient.removeQueries({
queryKey: [QueryKey.JsonSchema, org, app, respectiveFileNameInXsdOrJson],
});
await deleteDatamodel(org, app, modelPath);
},
onSuccess: (data, variables) => {
queryClient.setQueryData(
[QueryKey.JsonSchema, org, app, respectiveFileNameInXsdOrJson],
undefined,
[QueryKey.DatamodelsJson, org, app],
(oldData: DatamodelMetadata[]) => removeDatamodelFromList(oldData, variables),
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
);
await Promise.all([
queryClient.invalidateQueries({ queryKey: [QueryKey.DatamodelsJson, org, app] }),
queryClient.invalidateQueries({ queryKey: [QueryKey.DatamodelsXsd, org, app] }),
]);
queryClient.setQueryData([QueryKey.DatamodelsXsd, org, app], (oldData: DatamodelMetadata[]) =>
removeDatamodelFromList(oldData, variables),
);
queryClient.removeQueries({ queryKey: [QueryKey.JsonSchema, org, app, variables] });
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
},
});
};

export const removeDatamodelFromList = (
datamodels: DatamodelMetadata[],
relativeUrl: string,
): DatamodelMetadata[] =>
datamodels.filter((datamodel) => datamodel.repositoryRelativeUrl !== relativeUrl);
TomasEng marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading