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 all 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,20 +6,37 @@ 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 {
createJsonMetadataMock,
createXsdMetadataMock,
} 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';
import type { SchemaEditorAppProps } from '@altinn/schema-editor/SchemaEditorApp';
import { QueryKey } from 'app-shared/types/QueryKey';
import { createApiErrorMock } from 'app-shared/mocks/apiErrorMock';
import { createJsonModelPathMock } from 'app-shared/mocks/modelPathMocks';
import type {
DatamodelMetadataJson,
DatamodelMetadataXsd,
} from 'app-shared/types/DatamodelMetadata';
import { verifyNeverOccurs } from '../../../../testing/testUtils';

const user = userEvent.setup();

// Test data:
const modelPath = datamodelNameMock;
const model1Name = 'model1';
const model2name = 'model2';
const model1Path = createJsonModelPathMock(model1Name);
const model2Path = createJsonModelPathMock(model2name);
const model1MetadataJson: DatamodelMetadataJson = createJsonMetadataMock(model1Name);
const model1MetadataXsd: DatamodelMetadataXsd = createXsdMetadataMock(model1Name);
const model2MetadataJson: DatamodelMetadataJson = createJsonMetadataMock(model2name);
const model2MetadataXsd: DatamodelMetadataXsd = createXsdMetadataMock(model2name);

const defaultProps: SelectedSchemaEditorProps = {
modelPath,
modelPath: model1Path,
};
const org = 'org';
const app = 'app';
Expand Down Expand Up @@ -72,6 +89,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 @@ -80,7 +98,7 @@ describe('SelectedSchemaEditor', () => {

act(() => jest.advanceTimersByTime(AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS));
await waitFor(() => expect(saveDatamodel).toHaveBeenCalledTimes(1));
expect(saveDatamodel).toHaveBeenCalledWith(org, app, modelPath, dataMock);
expect(saveDatamodel).toHaveBeenCalledWith(org, app, model1Path, dataMock);
});

it('Autosaves when changing between models that are not present in the cache', async () => {
Expand All @@ -92,22 +110,19 @@ describe('SelectedSchemaEditor', () => {
await waitForElementToBeRemoved(() => screen.queryByTitle(textMock('general.loading')));
expect(saveDatamodel).not.toHaveBeenCalled();

const updatedProps = {
...defaultProps,
modelPath: 'newModel',
};
const updatedProps = { ...defaultProps, modelPath: model2Path };
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, model1Path, 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, newModelPath], dataMock);
queryClient.setQueryData([QueryKey.JsonSchema, org, app, model1Path], dataMock);
queryClient.setQueryData([QueryKey.JsonSchema, org, app, model1Path], dataMock);
const {
renderResult: { rerender },
} = render({ saveDatamodel }, queryClient);
Expand All @@ -120,17 +135,48 @@ 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, model1Path, dataMock);
});

it('Does not save when model is deleted', async () => {
const saveDatamodel = jest.fn();
const queryClient = createQueryClientMock();

queryClient.setQueryData([QueryKey.JsonSchema, org, app, model1Path], dataMock);
queryClient.setQueryData([QueryKey.JsonSchema, org, app, model2Path], dataMock);
const {
renderResult: { rerender },
} = render({ saveDatamodel }, queryClient);
expect(saveDatamodel).not.toHaveBeenCalled();

const updatedProps = {
...defaultProps,
modelPath: model2Path,
};
queryClient.setQueryData([QueryKey.DatamodelsJson, org, app], [model2MetadataJson]);
queryClient.setQueryData([QueryKey.DatamodelsXsd, org, app], [model2MetadataXsd]);
rerender(<SelectedSchemaEditor {...updatedProps} />);
jest.advanceTimersByTime(AUTOSAVE_DEBOUNCE_INTERVAL_MILLISECONDS);
await verifyNeverOccurs(() => expect(saveDatamodel).toHaveBeenCalled());
});
});

const render = (
queries: Partial<ServicesContextProps> = {},
queryClient = createQueryClientMock(),
props: Partial<SelectedSchemaEditorProps> = {},
) =>
renderWithMockStore(
) => {
queryClient.setQueryData(
[QueryKey.DatamodelsJson, org, app],
[model1MetadataJson, model2MetadataJson],
);
queryClient.setQueryData(
[QueryKey.DatamodelsXsd, org, app],
[model1MetadataXsd, model2MetadataXsd],
);
return renderWithMockStore(
{},
queries,
queryClient,
)(<SelectedSchemaEditor {...defaultProps} {...props} />);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ 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 +53,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 @@ -68,9 +77,24 @@ const SchemaEditorWithDebounce = ({ jsonSchema, modelPath }: SchemaEditorWithDeb
[saveFunction],
);

const doesModelExist = useCallback(() => {
const jsonModels: DatamodelMetadataJson[] = queryClient.getQueryData([
QueryKey.DatamodelsJson,
org,
app,
]);
const xsdModels: DatamodelMetadataXsd[] = queryClient.getQueryData([
QueryKey.DatamodelsXsd,
org,
app,
]);
const metadataList = mergeJsonAndXsdData(jsonModels, xsdModels);
return metadataList.some((datamodel) => datamodel.repositoryRelativeUrl === modelPath);
}, [queryClient, org, app, modelPath]);

useOnUnmount(() => {
clearTimeout(saveTimeoutRef.current);
saveFunction();
if (doesModelExist()) 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,71 @@
import { renderHookWithMockStore } from '../../test/mocks';
import { 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 { waitFor } from '@testing-library/react';
import { QueryKey } from 'app-shared/types/QueryKey';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import { createJsonModelPathMock } from 'app-shared/mocks/modelPathMocks';
import {
createJsonMetadataMock,
createXsdMetadataMock,
} from 'app-shared/mocks/datamodelMetadataMocks';

const modelName = 'modelName';
const modelPath = createJsonModelPathMock(modelName);
const org = 'org';
const app = 'app';
const modelMetadataJson = createJsonMetadataMock(modelName);
const modelMetadataXsd = createXsdMetadataMock(modelName);

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

it('Calls deleteDatamodel with correct parameters', async () => {
const client = createQueryClientMock();
client.setQueryData([QueryKey.DatamodelsJson, org, app], [modelMetadataJson]);
client.setQueryData([QueryKey.DatamodelsXsd, org, app], [modelMetadataXsd]);
const {
renderHookResult: { result },
} = render({}, client);
expect(result.current).toBeDefined();
result.current.mutate(modelPath);
await waitFor(() => result.current.isSuccess);
expect(queriesMock.deleteDatamodel).toHaveBeenCalledTimes(1);
expect(queriesMock.deleteDatamodel).toHaveBeenCalledWith(org, app, modelPath);
});

it('Removes the metadata instances from the query cache', async () => {
const client = createQueryClientMock();
client.setQueryData([QueryKey.DatamodelsJson, org, app], [modelMetadataJson]);
client.setQueryData([QueryKey.DatamodelsXsd, org, app], [modelMetadataXsd]);
const {
renderHookResult: { result },
} = render({}, client);
result.current.mutate(modelPath);
await waitFor(() => result.current.isSuccess);
expect(client.getQueryData([QueryKey.DatamodelsJson, org, app])).toEqual([]);
expect(client.getQueryData([QueryKey.DatamodelsXsd, org, app])).toEqual([]);
});

it('Removes the schema queries from the query cache', async () => {
const client = createQueryClientMock();
client.setQueryData([QueryKey.DatamodelsJson, org, app], [modelMetadataJson]);
client.setQueryData([QueryKey.DatamodelsXsd, org, app], [modelMetadataXsd]);
const {
renderHookResult: { result },
} = render({}, client);
result.current.mutate(modelPath);
await waitFor(() => result.current.isSuccess);
expect(client.getQueryData([QueryKey.JsonSchema, org, app, modelPath])).toBeUndefined();
expect(
client.getQueryData([QueryKey.JsonSchema, org, app, modelMetadataXsd.repositoryRelativeUrl]),
).toBeUndefined();
});
});

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,41 @@ 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);
const respectiveFileNameInXsdOrJson = isXsdFile(modelPath)
const jsonSchemaPath = isXsdFile(modelPath)
? modelPath.replace('.xsd', '.schema.json')
: modelPath.replace('.schema.json', '.xsd');
queryClient.setQueryData([QueryKey.JsonSchema, org, app, modelPath], undefined);
: modelPath;
const xsdPath = isXsdFile(modelPath) ? modelPath : modelPath.replace('.schema.json', '.xsd');
queryClient.setQueryData(
[QueryKey.JsonSchema, org, app, respectiveFileNameInXsdOrJson],
undefined,
[QueryKey.DatamodelsJson, org, app],
(oldData: DatamodelMetadata[]) => removeDatamodelFromList(oldData, jsonSchemaPath),
);
queryClient.setQueryData([QueryKey.DatamodelsXsd, org, app], (oldData: DatamodelMetadata[]) =>
removeDatamodelFromList(oldData, xsdPath),
);
await Promise.all([
queryClient.invalidateQueries({ queryKey: [QueryKey.DatamodelsJson, org, app] }),
queryClient.invalidateQueries({ queryKey: [QueryKey.DatamodelsXsd, org, app] }),
]);
await deleteDatamodel(org, app, modelPath);
return { jsonSchemaPath, xsdPath };
},
onSuccess: ({ jsonSchemaPath, xsdPath }) => {
queryClient.removeQueries({
queryKey: [QueryKey.JsonSchema, org, app, jsonSchemaPath],
});
queryClient.removeQueries({
queryKey: [QueryKey.JsonSchema, org, app, xsdPath],
});
},
});
};

export const removeDatamodelFromList = (
datamodels: DatamodelMetadata[],
relativeUrl: string,
): DatamodelMetadata[] =>
datamodels.filter((datamodel) => datamodel.repositoryRelativeUrl !== relativeUrl);
24 changes: 14 additions & 10 deletions frontend/packages/shared/src/mocks/datamodelMetadataMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
DatamodelMetadataJson,
DatamodelMetadataXsd,
} from 'app-shared/types/DatamodelMetadata';
import { createJsonModelPathMock, createXsdModelPathMock } from 'app-shared/mocks/modelPathMocks';

export const datamodelNameMock = 'model1';
const description = null;
Expand All @@ -15,18 +16,21 @@ const metadataMockBase = {
lastChanged,
};

export const jsonMetadataMock: DatamodelMetadataJson = {
export const createJsonMetadataMock = (modelName: string): DatamodelMetadataJson => ({
...metadataMockBase,
fileName: `${datamodelNameMock}.schema.json`,
filePath: `${directory}/${datamodelNameMock}.schema.json`,
fileName: `${modelName}.schema.json`,
filePath: `${directory}/${modelName}.schema.json`,
fileType: '.json',
repositoryRelativeUrl: `/App/models/${datamodelNameMock}.schema.json`,
};
repositoryRelativeUrl: createJsonModelPathMock(modelName),
});

export const xsdMetadataMock: DatamodelMetadataXsd = {
export const createXsdMetadataMock = (modelName: string): DatamodelMetadataXsd => ({
...metadataMockBase,
fileName: `${datamodelNameMock}.xsd`,
filePath: `${directory}/${datamodelNameMock}.xsd`,
fileName: `${modelName}.xsd`,
filePath: `${directory}/${modelName}.xsd`,
fileType: '.xsd',
repositoryRelativeUrl: `/App/models/${datamodelNameMock}.xsd`,
};
repositoryRelativeUrl: createXsdModelPathMock(modelName),
});

export const jsonMetadataMock: DatamodelMetadataJson = createJsonMetadataMock(datamodelNameMock);
export const xsdMetadataMock: DatamodelMetadataXsd = createXsdMetadataMock(datamodelNameMock);
2 changes: 2 additions & 0 deletions frontend/packages/shared/src/mocks/modelPathMocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const createJsonModelPathMock = (name: string): string => `/App/models/${name}.schema.json`;
export const createXsdModelPathMock = (name: string): string => `/App/models/${name}.xsd`;
3 changes: 3 additions & 0 deletions frontend/testing/testUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { waitFor } from '@testing-library/react';
import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants';

export const TEST_DOMAIN = 'http://localhost';
Expand All @@ -8,3 +9,5 @@ export const setWindowLocationForTests = (org: string, app: string) => {
`${TEST_DOMAIN}${APP_DEVELOPMENT_BASENAME}/${org}/${app}`,
) as unknown as Location;
};

export const verifyNeverOccurs = (fn: () => void) => expect(waitFor(fn)).rejects.toThrow();
Loading