Skip to content

Commit

Permalink
Merge branch 'feat/13686-support-access-packages-in-policy-editor-v1'…
Browse files Browse the repository at this point in the history
… of https://github.com/Altinn/altinn-studio into feat/13686-support-access-packages-in-policy-editor-v1
  • Loading branch information
mgunnerud committed Dec 18, 2024
2 parents 90c258a + 6c3d4a5 commit 67a78f8
Show file tree
Hide file tree
Showing 35 changed files with 638 additions and 227 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';

const uploadCodeListButtonTextMock = 'Upload Code List';
const updateCodeListButtonTextMock = 'Update Code List';
const updateCodeListIdButtonTextMock = 'Update Code List Id';
const codeListNameMock = 'codeListNameMock';
const newCodeListNameMock = 'newCodeListNameMock';
const codeListMock: CodeList = [{ value: '', label: '' }];
jest.mock(
'../../../libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage',
() => ({
CodeListPage: ({ onUpdateCodeList, onUploadCodeList }: any) => (
CodeListPage: ({ onUpdateCodeList, onUploadCodeList, onUpdateCodeListId }: any) => (
<div>
<button
onClick={() =>
Expand All @@ -36,6 +38,9 @@ jest.mock(
>
{updateCodeListButtonTextMock}
</button>
<button onClick={() => onUpdateCodeListId(codeListNameMock, newCodeListNameMock)}>
{updateCodeListIdButtonTextMock}
</button>
</div>
),
}),
Expand Down Expand Up @@ -115,6 +120,23 @@ describe('AppContentLibrary', () => {
codeListMock,
);
});

it('calls onUpdateOptionListId when onUpdateCodeListId is triggered', async () => {
const user = userEvent.setup();
renderAppContentLibrary(optionListsMock);
await goToLibraryPage(user, 'code_lists');
const updateCodeListIdButton = screen.getByRole('button', {
name: updateCodeListIdButtonTextMock,
});
await user.click(updateCodeListIdButton);
expect(queriesMock.updateOptionListId).toHaveBeenCalledTimes(1);
expect(queriesMock.updateOptionListId).toHaveBeenCalledWith(
org,
app,
codeListNameMock,
newCodeListNameMock,
);
});
});

const getLibraryPageTile = (libraryPage: string) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmen
import { convertOptionListsToCodeLists } from './utils/convertOptionListsToCodeLists';
import { StudioPageSpinner } from '@studio/components';
import { useTranslation } from 'react-i18next';
import { useAddOptionListMutation, useUpdateOptionListMutation } from 'app-shared/hooks/mutations';
import type { ApiError } from 'app-shared/types/api/ApiError';
import { toast } from 'react-toastify';
import type { AxiosError } from 'axios';
import { isErrorUnknown } from 'app-shared/utils/ApiErrorUtils';
import {
useAddOptionListMutation,
useUpdateOptionListMutation,
useUpdateOptionListIdMutation,
} from 'app-shared/hooks/mutations';

export function AppContentLibrary(): React.ReactElement {
const { org, app } = useStudioEnvironmentParams();
Expand All @@ -24,12 +28,17 @@ export function AppContentLibrary(): React.ReactElement {
hideDefaultError: (error: AxiosError<ApiError>) => isErrorUnknown(error),
});
const { mutate: updateOptionList } = useUpdateOptionListMutation(org, app);
const { mutate: updateOptionListId } = useUpdateOptionListIdMutation(org, app);

if (optionListsPending)
return <StudioPageSpinner spinnerTitle={t('general.loading')}></StudioPageSpinner>;

const codeLists = convertOptionListsToCodeLists(optionLists);

const handleUpdateCodeListId = (optionListId: string, newOptionListId: string) => {
updateOptionListId({ optionListId, newOptionListId });
};

const handleUpload = (file: File) => {
uploadOptionList(file, {
onSuccess: () => {
Expand All @@ -52,6 +61,7 @@ export function AppContentLibrary(): React.ReactElement {
codeList: {
props: {
codeLists: codeLists,
onUpdateCodeListId: handleUpdateCodeListId,
onUpdateCodeList: handleUpdate,
onUploadCodeList: handleUpload,
fetchDataError: optionListsError,
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { StudioButton } from '@studio/components';
import { TrashIcon } from '@studio/icons';
import { useDeleteDataModelMutation } from '../../../../../hooks/mutations';
import type { MetadataOption } from '../../../../../types/MetadataOption';
Expand Down Expand Up @@ -43,18 +42,15 @@ export function DeleteWrapper({ selectedOption }: DeleteWrapperProps) {
confirmText={t('schema_editor.confirm_deletion')}
onConfirm={onDeleteConfirmClick}
onClose={() => setDialogOpen(false)}
trigger={
<StudioButton
id='delete-model-button'
disabled={!schemaName}
onClick={onDeleteClick}
color='danger'
icon={<TrashIcon />}
variant='tertiary'
>
{t('schema_editor.delete_data_model')}
</StudioButton>
}
triggerProps={{
id: 'delete-model-button',
disabled: !schemaName,
onClick: onDeleteClick,
color: 'danger',
icon: <TrashIcon />,
variant: 'tertiary',
children: t('schema_editor.delete_data_model'),
}}
>
<p>
<Trans
Expand Down
4 changes: 4 additions & 0 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
"api_errors.ResourceNotFound": "Fant ikke en fil som appen din prøvde å få tak i.",
"api_errors.Unauthorized": "Handlingen du prøver å utføre krever rettigheter du ikke har. Du blir nå logget ut.",
"api_errors.UploadedImageNotValid": "Det opplastede bildet er ikke en gyldig filtype",
"app_content_library.code_lists.code_list_accordion_title": "Kodelistenavn: {{codeListTitle}}",
"app_content_library.code_lists.code_list_edit_id_label": "Navn på kodeliste",
"app_content_library.code_lists.code_list_edit_id_title": "Rediger navn på kodelisten {{codeListName}}",
"app_content_library.code_lists.code_list_view_id_title": "Navn på kodeliste: {{codeListName}}",
"app_content_library.code_lists.code_lists_count_info_none": "Det finnes ingen kodelister i biblioteket.",
"app_content_library.code_lists.code_lists_count_info_plural": "Det finnes <bold>{{codeListsCount}}</bold> kodelister i biblioteket.",
"app_content_library.code_lists.code_lists_count_info_single": "Det finnes <bold>1</bold> kodeliste i biblioteket.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { ForwardedRef } from 'react';
import React from 'react';
import type { RenderResult } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { StudioPageHeader } from '../';
import { StudioPopover } from '../../';
import userEvent from '@testing-library/user-event';
import { testRefForwarding } from '../../../test-utils/testRefForwarding';
import type { StudioPageHeaderPopoverTriggerProps } from './StudioPageHeaderPopoverTrigger';
import { testRootClassNameAppending } from '../../../test-utils/testRootClassNameAppending';
import { testCustomAttributes } from '../../../test-utils/testCustomAttributes';

// Test data:
const triggerText = 'Trigger';
const contentText = 'Content';
const defaultProps: StudioPageHeaderPopoverTriggerProps = {
children: triggerText,
};

describe('StudioPageHeader.PopoverTrigger', () => {
it('Renders the trigger button', () => {
renderPopover();
expect(screen.getByRole('button', { name: triggerText })).toBeInTheDocument();
});

it('Does not display the popover by default', () => {
renderPopover();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});

it('Opens the popover when the user clicks the trigger', async () => {
const user = userEvent.setup();
renderPopover();
await user.click(screen.getByRole('button', { name: triggerText }));
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toHaveTextContent(contentText);
});

it('Forwards the ref to the trigger button', () => {
testRefForwarding<HTMLButtonElement>((ref) => renderPopoverTrigger({}, ref));
});

it('Appends the given className to the trigger button', () => {
testRootClassNameAppending((className) => renderPopoverTrigger({ className }));
});

it('Accepts custom attributes', () => {
testCustomAttributes(renderPopoverTrigger);
});
});

function renderPopover(): RenderResult {
return render(
<StudioPopover>
<StudioPageHeader.PopoverTrigger {...defaultProps} />
<StudioPopover.Content>{contentText}</StudioPopover.Content>
</StudioPopover>,
);
}

function renderPopoverTrigger(
props: StudioPageHeaderPopoverTriggerProps,
ref?: ForwardedRef<HTMLButtonElement>,
): RenderResult {
return render(<StudioPageHeader.PopoverTrigger {...defaultProps} {...props} ref={ref} />);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { forwardRef } from 'react';
import type { StudioPopoverTriggerProps } from '../../StudioPopover';
import { StudioPopover } from '../../StudioPopover';
import cn from 'classnames';
import classes from '../common.module.css';
import type { StudioPageHeaderColor } from '../types/StudioPageHeaderColor';
import type { StudioPageHeaderVariant } from '../types/StudioPageHeaderVariant';

export type StudioPageHeaderPopoverTriggerProps = {
color?: StudioPageHeaderColor;
variant?: StudioPageHeaderVariant;
} & Omit<StudioPopoverTriggerProps, 'color' | 'variant'>;

export const StudioPageHeaderPopoverTrigger = forwardRef<
HTMLButtonElement,
StudioPageHeaderPopoverTriggerProps
>(
(
{ color = 'dark', variant = 'regular', className: givenClass, ...rest },
ref,
): React.ReactElement => {
const className = cn(classes.linkOrButton, classes[variant], classes[color], givenClass);
return <StudioPopover.Trigger className={className} {...rest} ref={ref} />;
},
);

StudioPageHeaderPopoverTrigger.displayName = 'StudioPageHeader.PopoverTrigger';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './StudioPageHeaderPopoverTrigger';
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import { StudioPageHeaderMain } from './StudioPageHeaderMain';
import { StudioPageHeaderRight } from './StudioPageHeaderRight';
import { StudioPageHeaderSub } from './StudioPageHeaderSub';
import { StudioPageHeaderHeaderLink } from './StudioPageHeaderHeaderLink';
import { StudioPageHeaderPopoverTrigger } from './StudioPageHeaderPopoverTrigger';

type StudioPageHeaderComponent = typeof StudioPageHeaderParent & {
Main: typeof StudioPageHeaderMain;
Left: typeof StudioPageHeaderLeft;
Center: typeof StudioPageHeaderCenter;
Right: typeof StudioPageHeaderRight;
PopoverTrigger: typeof StudioPageHeaderPopoverTrigger;
Sub: typeof StudioPageHeaderSub;
HeaderButton: typeof StudioPageHeaderHeaderButton;
HeaderLink: typeof StudioPageHeaderHeaderLink;
Expand All @@ -32,6 +34,7 @@ StudioPageHeader.Main = StudioPageHeaderMain;
StudioPageHeader.Left = StudioPageHeaderLeft;
StudioPageHeader.Center = StudioPageHeaderCenter;
StudioPageHeader.Right = StudioPageHeaderRight;
StudioPageHeader.PopoverTrigger = StudioPageHeaderPopoverTrigger;
StudioPageHeader.Sub = StudioPageHeaderSub;
StudioPageHeader.HeaderButton = StudioPageHeaderHeaderButton;
StudioPageHeader.HeaderLink = StudioPageHeaderHeaderLink;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
import React from 'react';
import {
type PopoverProps,
Popover,
type PopoverTriggerProps,
type PopoverContentProps,
} from '@digdir/designsystemet-react';

const StudioPopoverTrigger = ({ ...rest }: PopoverTriggerProps): React.ReactElement => {
return <Popover.Trigger {...rest} />;
};
import React, { forwardRef } from 'react';
import { type PopoverProps, Popover, type PopoverContentProps } from '@digdir/designsystemet-react';
import type { WithoutAsChild } from '../../types/WithoutAsChild';
import type { StudioButtonProps } from '../StudioButton';
import { StudioButton } from '../StudioButton';

const StudioPopoverContent = ({ ...rest }: PopoverContentProps): React.ReactElement => {
return <Popover.Content {...rest} />;
};
export type StudioPopoverTriggerProps = StudioButtonProps;

export type StudioPopoverProps = PopoverProps;
const StudioPopoverTrigger = forwardRef<HTMLButtonElement, StudioPopoverTriggerProps>(
(props, ref): React.ReactElement => (
<Popover.Trigger asChild>
<StudioButton {...props} ref={ref} />
</Popover.Trigger>
),
);

const StudioPopoverRoot = ({ ...rest }: StudioPopoverProps): React.ReactElement => {
return <Popover {...rest} />;
};
StudioPopoverTrigger.displayName = 'StudioPopover.Trigger';

export type StudioPopoverContentProps = WithoutAsChild<PopoverContentProps>;

const StudioPopoverContent = forwardRef<HTMLDivElement, StudioPopoverContentProps>(
(props, ref): React.ReactElement => <Popover.Content {...props} ref={ref} />,
);

StudioPopoverContent.displayName = 'StudioPopover.Content';

export type StudioPopoverProps = WithoutAsChild<PopoverProps>;

function StudioPopoverRoot(props: StudioPopoverProps): React.ReactElement {
return <Popover {...props} />;
}

type StudioPopoverComponent = typeof StudioPopoverRoot & {
Trigger: typeof StudioPopoverTrigger;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { StudioPopover } from './StudioPopover';
export type { StudioPopoverProps } from './StudioPopover';
export * from './StudioPopover';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type WithoutAsChild<Props> = Omit<Props, 'asChild'>;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const mockPagesConfig: PagesConfig = {
{ title: 'CodeList1', codeList: [] },
{ title: 'CodeList2', codeList: [] },
],
onUpdateCodeListId: () => {},
onUpdateCodeList: () => {},
onUploadCodeList: () => {},
fetchDataError: false,
Expand Down
Loading

0 comments on commit 67a78f8

Please sign in to comment.