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

resourceadm: add broker service resource type #12643

Merged
merged 12 commits into from
Apr 9, 2024
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')}
mgunnerud marked this conversation as resolved.
Show resolved Hide resolved
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
Loading