Skip to content

Commit

Permalink
resourceadm: add broker service resource type (#12643)
Browse files Browse the repository at this point in the history
* add broker service resource type

* do not allow empty space as resource version

* sort link services for import + show service owner code in import combobox

* show resource id and list id as readonly field

* show resource identifier in resource table

* add UU improvements to RRR table
  • Loading branch information
mgunnerud authored Apr 9, 2024
1 parent 38f9e9d commit d1e14c4
Show file tree
Hide file tree
Showing 20 changed files with 122 additions and 46 deletions.
6 changes: 6 additions & 0 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,8 @@
"resourceadm.about_resource_error_usage_string_title": "tittel",
"resourceadm.about_resource_homepage_label": "Hjemmeside",
"resourceadm.about_resource_homepage_text": "Lenke til informasjon om hvor sluttbruker kan finne tjenesten og informasjon om den.",
"resourceadm.about_resource_identifier_description": "Dette er en unik ID for ressursen både i URler og i APIer.",
"resourceadm.about_resource_identifier_label": "Ressurs-id",
"resourceadm.about_resource_keywords_label": "Nøkkelord",
"resourceadm.about_resource_keywords_text": "Ord som er enkle å søke på. Separer hvert ord med komma \",\".",
"resourceadm.about_resource_langauge_error_missing_1": "Du mangler oversettelse for {{usageString}} på {{lang}}.",
Expand Down Expand Up @@ -877,6 +879,7 @@
"resourceadm.about_resource_resource_title_label": "Navn på tjenesten (Bokmål)",
"resourceadm.about_resource_resource_title_text": "Navnet vil synes for brukere, og må være gjenkjennelig og beskrivende for hva tjenesten handler om. Pass på å skille tjenester som har like eller nesten like navn, slik at det blir lett for brukere å forstå.",
"resourceadm.about_resource_resource_type": "Ressurstype",
"resourceadm.about_resource_resource_type_brokerservice": "Formidlingstjeneste",
"resourceadm.about_resource_resource_type_error": "Du mangler å legge til ressurstype.",
"resourceadm.about_resource_resource_type_generic_access_resource": "Generisk tilgangsressurs",
"resourceadm.about_resource_resource_type_label": "Velg en ressurstype fra listen under.",
Expand Down Expand Up @@ -938,6 +941,7 @@
"resourceadm.dashboard_table_header_last_changed": "Sist endret",
"resourceadm.dashboard_table_header_name": "Navn",
"resourceadm.dashboard_table_header_policy_rules": "Tilgangsregler",
"resourceadm.dashboard_table_header_resourceid": "Ressurs-id",
"resourceadm.dashboard_table_row_edit": "Rediger ressurs",
"resourceadm.dashboard_table_row_has_policy": "Har tilgangsregler",
"resourceadm.dashboard_table_row_missing_policy": "Mangler tilgangsregler",
Expand Down Expand Up @@ -981,6 +985,7 @@
"resourceadm.left_nav_bar_organization_access": "Organisasjonstilganger",
"resourceadm.left_nav_bar_policy": "Tilgangsregler",
"resourceadm.listadmin_add_to_list": "Legg til i liste",
"resourceadm.listadmin_add_to_list_org": "Legg til {{org}} i liste",
"resourceadm.listadmin_back": "Tilbake til dashboard",
"resourceadm.listadmin_confirm_create_list": "Opprett tilgangsliste",
"resourceadm.listadmin_create_list": "Opprett ny tilgangsliste",
Expand Down Expand Up @@ -1014,6 +1019,7 @@
"resourceadm.listadmin_parties": "Enheter",
"resourceadm.listadmin_party": "Enhet",
"resourceadm.listadmin_remove_from_list": "Fjern fra liste",
"resourceadm.listadmin_remove_from_list_org": "Fjern {{org}} fra liste",
"resourceadm.listadmin_resource_header": "Tilgangslister for {{resourceTitle}} - {{env}}",
"resourceadm.listadmin_resource_list_checkbox_header": "Alle enheter og underenheter i valgte liste(r) kan bruke ressursen",
"resourceadm.listadmin_search": "Søk for å legge til ny enhet eller underenhet",
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/shared/src/types/Altinn2LinkService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Altinn2LinkService {
serviceOwnerCode: string;
serviceName: string;
externalServiceCode: string;
externalServiceEditionCode: string;
Expand Down
6 changes: 5 additions & 1 deletion frontend/packages/shared/src/types/ResourceAdm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ export interface ResourceContactPoint {
contactPage: string;
}

export type ResourceTypeOption = 'GenericAccessResource' | 'Systemresource' | 'MaskinportenSchema';
export type ResourceTypeOption =
| 'GenericAccessResource'
| 'Systemresource'
| 'MaskinportenSchema'
| 'BrokerService';

export type ResourceStatusOption = 'Completed' | 'Deprecated' | 'UnderDevelopment' | 'Withdrawn';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const AccessListDetail = ({
label={t('resourceadm.listadmin_list_id')}
description={t('resourceadm.listadmin_list_id_description')}
>
<Textfield value={list.identifier} disabled />
<Textfield value={list.identifier} readOnly />
</FieldWrapper>
<FieldWrapper
fieldId='listname'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import type { AxiosError } from 'axios';
import { Alert } from '@digdir/design-system-react';
import { getEnvLabel } from 'resourceadm/utils/resourceUtils';
import { type EnvId } from 'resourceadm/utils/resourceUtils';
import { getEnvLabel } from '../../utils/resourceUtils';
import { type EnvId } from '../../utils/resourceUtils';

interface AccessListErrorMessageProps {
error: AxiosError;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { usePartiesRegistryQuery } from '../../hooks/queries/usePartiesRegistryQ
import { useSubPartiesRegistryQuery } from '../../hooks/queries/useSubPartiesRegistryQuery';
import { getPartiesQueryUrl } from '../../utils/urlUtils';
import { StudioSpinner, StudioButton } from '@studio/components';
import { PlusIcon, PlusCircleIcon, MinusCircleIcon } from '@studio/icons';
import { PlusIcon } from '@studio/icons';
import { AccessListMembersPaging } from './AccessListMembersPaging';
import { AccessListMembersTable } from './AccessListMembersTable';

Expand Down Expand Up @@ -71,12 +71,6 @@ export const AccessListMembers = ({
>
<AccessListMembersTable
listItems={listItems}
buttonNode={
<>
{t('resourceadm.listadmin_remove_from_list')}
<MinusCircleIcon className={classes.buttonIcon} />
</>
}
onButtonClick={(item: AccessListMember) => handleRemoveMember(item.orgNr)}
/>
{listItems.length === 0 && (
Expand Down Expand Up @@ -115,15 +109,8 @@ export const AccessListMembers = ({
<AccessListMembersTable
isHeaderHidden
listItems={resultData?.parties ?? []}
disableButtonFn={(disableItem: AccessListMember) =>
!!listItems.find((listItem) => disableItem.orgNr === listItem.orgNr)
}
buttonNode={
<>
{t('resourceadm.listadmin_add_to_list')}
<PlusCircleIcon className={classes.buttonIcon} />
</>
}
disabledItems={listItems}
isAdd
onButtonClick={handleAddMember}
/>
{(isLoadingParties || isLoadingSubParties) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,57 @@ import { Table } from '@digdir/design-system-react';
import type { AccessListMember } from 'app-shared/types/ResourceAdm';
import { StudioButton } from '@studio/components';
import classes from './AccessListMembers.module.css';
import { PlusCircleIcon, MinusCircleIcon } from '@studio/icons';
import { stringNumberToAriaLabel } from '../../utils/stringUtils';

interface AccessListMembersTableProps {
listItems: AccessListMember[];
buttonNode: React.JSX.Element;
isAdd?: boolean;
isHeaderHidden?: boolean;
disableButtonFn?: (member: AccessListMember) => boolean;
disabledItems?: AccessListMember[];
onButtonClick: (member: AccessListMember) => void;
}

export const AccessListMembersTable = ({
listItems,
buttonNode,
isAdd,
isHeaderHidden,
disableButtonFn,
disabledItems,
onButtonClick,
}: AccessListMembersTableProps): React.JSX.Element => {
const { t } = useTranslation();

const renderActionButton = (item: AccessListMember): React.JSX.Element => {
let buttonAriaLabel: string;
let buttonIcon: React.JSX.Element;
let buttonText: string;
if (isAdd) {
buttonAriaLabel = t('resourceadm.listadmin_add_to_list_org', { org: item.orgName });
buttonIcon = <PlusCircleIcon className={classes.buttonIcon} />;
buttonText = t('resourceadm.listadmin_add_to_list');
} else {
buttonAriaLabel = t('resourceadm.listadmin_remove_from_list_org', {
org: item.orgName,
});
buttonIcon = <MinusCircleIcon className={classes.buttonIcon} />;
buttonText = t('resourceadm.listadmin_remove_from_list');
}
return (
<StudioButton
aria-label={buttonAriaLabel}
onClick={() => onButtonClick(item)}
disabled={
disabledItems && disabledItems.some((existingItem) => existingItem.orgNr === item.orgNr)
}
variant='tertiary'
size='small'
>
{buttonText}
{buttonIcon}
</StudioButton>
);
};

return (
<Table size='small' className={classes.membersTable}>
<Table.Head className={isHeaderHidden ? classes.hiddenHeader : undefined}>
Expand All @@ -42,23 +75,14 @@ export const AccessListMembersTable = ({
{listItems.map((item) => {
return (
<Table.Row key={item.orgNr}>
<Table.Cell>{item.orgNr}</Table.Cell>
<Table.Cell aria-label={stringNumberToAriaLabel(item.orgNr)}>{item.orgNr}</Table.Cell>
<Table.Cell>{item.orgName || t('resourceadm.listadmin_empty_name')}</Table.Cell>
<Table.Cell>
{item.isSubParty
? t('resourceadm.listadmin_sub_party')
: t('resourceadm.listadmin_party')}
</Table.Cell>
<Table.Cell>
<StudioButton
onClick={() => onButtonClick(item)}
disabled={disableButtonFn ? disableButtonFn(item) : false}
variant='tertiary'
size='small'
>
{buttonNode}
</StudioButton>
</Table.Cell>
<Table.Cell>{renderActionButton(item)}</Table.Cell>
</Table.Row>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import type { Altinn2LinkService } from 'app-shared/types/Altinn2LinkService';
import { ServerCodes } from 'app-shared/enums/ServerCodes';

const mockAltinn2LinkService: Altinn2LinkService = {
serviceOwnerCode: 'ttd',
externalServiceCode: 'code1',
externalServiceEditionCode: 'edition1',
serviceName: 'TestService',
};
const mockAltinn2LinkServices: Altinn2LinkService[] = [mockAltinn2LinkService];
const mockOption: string = `${mockAltinn2LinkService.externalServiceCode}-${mockAltinn2LinkService.externalServiceEditionCode}-${mockAltinn2LinkService.serviceName}`;
const mockOption: string = `${mockAltinn2LinkService.serviceOwnerCode}: ${mockAltinn2LinkService.externalServiceCode}-${mockAltinn2LinkService.externalServiceEditionCode}-${mockAltinn2LinkService.serviceName}`;

const mockOnClose = jest.fn();
const getAltinn2LinkServices = jest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ const mockSelectedContext: string = 'selectedContext';
const mockEnv: string = 'env1';

const mockAltinn2LinkService: Altinn2LinkService = {
serviceOwnerCode: 'ttd',
externalServiceCode: 'code1',
externalServiceEditionCode: 'edition1',
serviceName: 'TestService',
};
const mockAltinn2HyphenLinkService: Altinn2LinkService = {
serviceOwnerCode: 'ttd',
externalServiceCode: 'code2',
externalServiceEditionCode: 'edition2',
serviceName: 'test-med---hyphens',
Expand All @@ -28,8 +30,8 @@ const mockAltinn2LinkServices: Altinn2LinkService[] = [
mockAltinn2LinkService,
mockAltinn2HyphenLinkService,
];
const mockOption: string = `${mockAltinn2LinkService.externalServiceCode}-${mockAltinn2LinkService.externalServiceEditionCode}-${mockAltinn2LinkService.serviceName}`;
const mockHyphenOption: string = `${mockAltinn2HyphenLinkService.externalServiceCode}-${mockAltinn2HyphenLinkService.externalServiceEditionCode}-${mockAltinn2HyphenLinkService.serviceName}`;
const mockOption: string = `${mockAltinn2LinkService.serviceOwnerCode}: ${mockAltinn2LinkService.externalServiceCode}-${mockAltinn2LinkService.externalServiceEditionCode}-${mockAltinn2LinkService.serviceName}`;
const mockHyphenOption: string = `${mockAltinn2HyphenLinkService.serviceOwnerCode}: ${mockAltinn2HyphenLinkService.externalServiceCode}-${mockAltinn2HyphenLinkService.externalServiceEditionCode}-${mockAltinn2HyphenLinkService.serviceName}`;

const mockOnSelectService = jest.fn();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ type ResourceTextFieldProps = {
* Whether this field is required or not
*/
required?: boolean;
/**
* Whether this field is read only or not
*/
readOnly?: boolean;
};

/**
Expand All @@ -59,6 +63,7 @@ type ResourceTextFieldProps = {
* @property {boolean}[showErrorMessage] - Flag for if the error message should be shown
* @property {string}[errorText] - The text to be shown
* @property {boolean}[required] - Whether this field is required or not
* @property {boolean}[readOnly] - Whether this field is read only or not
*
* @returns {React.JSX.Element} - The rendered component
*/
Expand All @@ -74,6 +79,7 @@ export const ResourceTextField = forwardRef<HTMLInputElement, ResourceTextFieldP
showErrorMessage = false,
errorText,
required,
readOnly,
},
ref,
): React.JSX.Element => {
Expand All @@ -94,6 +100,7 @@ export const ResourceTextField = forwardRef<HTMLInputElement, ResourceTextFieldP
ref={ref}
onBlur={() => onBlur(val)}
required={required}
readOnly={readOnly}
/>
{showErrorMessage && <InputFieldErrorMessage message={errorText} />}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ export const ResourceTable = ({
headerName: t('resourceadm.dashboard_table_header_name'),
width: 200,
},
{
field: 'identifier',
headerName: t('resourceadm.dashboard_table_header_resourceid'),
width: 200,
},
{
field: 'createdBy',
headerName: t('resourceadm.dashboard_table_header_createdby'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ describe('AboutResourcePage', () => {
id: mockId,
};

it('handles resource id field blur', async () => {
render(<AboutResourcePage {...defaultProps} />);

const idInput = screen.getByLabelText(textMock('resourceadm.about_resource_identifier_label'));

await act(() => idInput.focus());
await act(() => idInput.blur());

expect(mockOnSaveResource).not.toHaveBeenCalled();
});

it('handles resource type change', async () => {
const user = userEvent.setup();
render(<AboutResourcePage {...defaultProps} />);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ export const AboutResourcePage = ({
<Heading size='large' level={1}>
{t('resourceadm.about_resource_title')}
</Heading>
<ResourceTextField
label={t('resourceadm.about_resource_identifier_label')}
description={t('resourceadm.about_resource_identifier_description')}
value={resourceData.identifier}
readOnly
onFocus={() => setTranslationType('none')}
onBlur={() => {}}
/>
<ResourceRadioGroup
label={t('resourceadm.about_resource_resource_type')}
description={t('resourceadm.about_resource_resource_type_label')}
Expand Down
2 changes: 1 addition & 1 deletion frontend/resourceadm/pages/ListAdminPage/ListAdminPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getAccessListPageUrl, getResourceDashboardURL } from '../../utils/urlUt
import { useUrlParams } from '../../hooks/useSelectedContext';
import type { EnvId } from '../../utils/resourceUtils';
import { getAvailableEnvironments, getEnvLabel } from '../../utils/resourceUtils';
import { AccessListErrorMessage } from 'resourceadm/components/AccessListErrorMessage';
import { AccessListErrorMessage } from '../../components/AccessListErrorMessage';

export const ListAdminPage = (): React.JSX.Element => {
const { t } = useTranslation();
Expand Down
2 changes: 1 addition & 1 deletion frontend/resourceadm/pages/ResourcePage/ResourcePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ export const ResourcePage = (): React.JSX.Element => {
onSaveVersion={(version: string) =>
handleSaveResource({
...resourceData,
version,
version: version?.trim(), // empty version is not allowed
})
}
id='page-content-deploy'
Expand Down
8 changes: 5 additions & 3 deletions frontend/resourceadm/utils/mapperUtils/mapperUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,25 @@ describe('mapperUtils', () => {
describe('mapAltinn2LinkServiceToSelectOption', () => {
const mockLinkServices: Altinn2LinkService[] = [
{
serviceOwnerCode: 'ttd',
externalServiceCode: 'code1',
externalServiceEditionCode: 'edition1',
serviceName: 'name1',
},
{
serviceOwnerCode: 'acn',
externalServiceCode: 'code2',
externalServiceEditionCode: 'edition2',
serviceName: 'name2',
},
];

it('should map Altinn2LinkService to SelectOption correctly', () => {
it('should map and sort Altinn2LinkService to SelectOption correctly', () => {
const result = mapAltinn2LinkServiceToSelectOption(mockLinkServices);

expect(result).toHaveLength(mockLinkServices.length);
expect(result[0].value).toBe(JSON.stringify(mockLinkServices[0]));
expect(result[0].label).toBe('code1-edition1-name1');
expect(result[0].value).toBe(JSON.stringify(mockLinkServices[1]));
expect(result[0].label).toBe('acn: code2-edition2-name2');
});
});
});
10 changes: 8 additions & 2 deletions frontend/resourceadm/utils/mapperUtils/mapperUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ export const sortResourceListByDate = (resourceList: ResourceListItem[]): Resour
* @returns an object that looks like this: { value: string, label: string }
*/
export const mapAltinn2LinkServiceToSelectOption = (linkServices: Altinn2LinkService[]) => {
return linkServices.map((ls: Altinn2LinkService) => ({
const sortedServices = [...linkServices].sort((a, b) => {
const serviceOwnerValue = a.serviceOwnerCode.localeCompare(b.serviceOwnerCode);
return serviceOwnerValue === 0
? a.externalServiceCode.localeCompare(b.externalServiceCode)
: serviceOwnerValue;
});
return sortedServices.map((ls: Altinn2LinkService) => ({
value: JSON.stringify(ls),
label: `${ls.externalServiceCode}-${ls.externalServiceEditionCode}-${ls.serviceName}`,
label: `${ls.serviceOwnerCode}: ${ls.externalServiceCode}-${ls.externalServiceEditionCode}-${ls.serviceName}`,
}));
};

Expand Down
Loading

0 comments on commit d1e14c4

Please sign in to comment.