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

Feat/am UI/195 add new user org #1232

Merged
merged 6 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -233,7 +233,7 @@ public async Task ValidatePerson_ValidInput()
var expectedResponse = Guid.Parse("167536b5-f8ed-4c5a-8f48-0279507e53ae");

// Act
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/person", content);
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/validateperson", content);

// Assert
Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
Expand Down Expand Up @@ -263,7 +263,7 @@ public async Task ValidatePerson_InvalidInput()
HttpContent content = new StringContent(jsonRights, Encoding.UTF8, "application/json");

// Act
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/person", content);
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/validateperson", content);

// Assert
Assert.Equal(HttpStatusCode.NotFound, httpResponse.StatusCode);
Expand All @@ -289,7 +289,7 @@ public async Task ValidatePerson_InvalidInputCombination()
HttpContent content = new StringContent(jsonRights, Encoding.UTF8, "application/json");

// Act
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/person", content);
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/validateperson", content);

// Assert
Assert.Equal(HttpStatusCode.NotFound, httpResponse.StatusCode);
Expand All @@ -315,10 +315,10 @@ public async Task ValidatePerson_TooManyRequests()
HttpContent content = new StringContent(jsonRights, Encoding.UTF8, "application/json");

// Act - reapeat the request 4 times to lock the user
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/person", content);
httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/person", content);
httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/person", content);
httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/person", content);
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/validateperson", content);
httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/validateperson", content);
httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/validateperson", content);
httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/validateperson", content);

// Assert
Assert.Equal(HttpStatusCode.TooManyRequests, httpResponse.StatusCode);
Expand All @@ -342,7 +342,7 @@ public async Task ValidatePerson_BadRequest()
HttpContent content = new StringContent(jsonRights, Encoding.UTF8, "application/json");

// Act
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/person", content);
HttpResponseMessage httpResponse = await _client.PostAsync($"accessmanagement/api/v1/user/reportee/{partyId}/rightholder/validateperson", content);

// Assert
Assert.Equal(HttpStatusCode.BadRequest, httpResponse.StatusCode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
[HttpGet]
[Authorize]
[Route("reporteelist/{partyId}")]
public async Task<ActionResult<AuthorizedParty>> GetPartyFromReporteeListIfExists(int partyId)

Check warning on line 72 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/UserController.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)

Check warning on line 72 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/UserController.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)
{
try
{
Expand Down Expand Up @@ -99,11 +99,11 @@
[HttpGet]
[Authorize]
[Route("reportee/{partyId}/rightholders")]
public async Task<ActionResult<List<RightHolder>>> GetReporteeRightHolders(int partyId)

Check warning on line 102 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/UserController.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)

Check warning on line 102 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/UserController.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

ModelState.IsValid should be checked in controller actions. (https://rules.sonarsource.com/csharp/RSPEC-6967)
{
try
{
string userPartyID = AuthenticationHelper.GetUserPartyId(_httpContextAccessor.HttpContext);

Check warning on line 106 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/UserController.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

Remove the unused local variable 'userPartyID'. (https://rules.sonarsource.com/csharp/RSPEC-1481)

Check warning on line 106 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/UserController.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

Remove the unused local variable 'userPartyID'. (https://rules.sonarsource.com/csharp/RSPEC-1481)

List<RightHolder> rightHolders = await _userService.GetReporteeRightHolders(partyId);

Expand All @@ -128,7 +128,7 @@
/// <response code="500">Internal Server Error</response>
[HttpPost]
[Authorize]
[Route("reportee/{partyId}/rightholder/person")]
[Route("reportee/{partyId}/rightholder/validateperson")]
public async Task<ActionResult<Guid>> ValidatePerson([FromBody] ValidatePersonInput validationInput)
{
if (!ModelState.IsValid)
Expand Down Expand Up @@ -162,7 +162,7 @@
}
catch (Exception ex)
{
_logger.LogError(ex, "GetUserByUUID failed to fetch party information");
_logger.LogError(ex, "ValidatePerson failed unexpectedly");
return StatusCode(500);
}
}
Expand All @@ -180,7 +180,7 @@
{
try
{
string userPartyID = AuthenticationHelper.GetUserPartyId(_httpContextAccessor.HttpContext);

Check warning on line 183 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI/Controllers/UserController.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

Remove the unused local variable 'userPartyID'. (https://rules.sonarsource.com/csharp/RSPEC-1481)

RightHolderAccesses accesses = await _userService.GetRightHolderAccesses(reporteeUuid, rightHolderUuid);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Meta, StoryObj } from '@storybook/react';

import { TechnicalErrorParagraphs } from './TechnicalErrorParagraphs';

export default {
title: 'Features/AMUI/TechnicalErrorParagraphs',
component: TechnicalErrorParagraphs,
render: (args) => (
<TechnicalErrorParagraphs
status='404'
time='2025-10-01T10:00:00Z'
{...args}
/>
),
} as Meta;

export const Default: StoryObj<typeof TechnicalErrorParagraphs> = {
args: { size: 'sm', status: '404', time: '2025-10-01T10:00:00Z' },
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { ParagraphProps } from '@digdir/designsystemet-react';
import { Paragraph } from '@digdir/designsystemet-react';
import React from 'react';
import { useTranslation } from 'react-i18next';

export interface TechnicalErrorParagraphsProps {
/*** The http status of the error */
status: string;
/*** The http status of the error */
time: string;
/*** The size of the paragraph text */
size?: ParagraphProps['size'];
}

export const TechnicalErrorParagraphs = ({
status,
time,
size = 'sm',
}: TechnicalErrorParagraphsProps) => {
const { t } = useTranslation();
return (
<>
<Paragraph
size={size}
variant='long'
>
{t('common.technical_error')}
</Paragraph>
<Paragraph size={size}>
{t('common.time_of_error', {
time: new Date(time).toLocaleDateString('nb-NO', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
}),
})}
</Paragraph>
<Paragraph size={size}>
{t('common.error_status', {
status: status,
})}
</Paragraph>
</>
);
};
1 change: 1 addition & 0 deletions src/features/amUI/common/TechnicalErrorParagraphs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TechnicalErrorParagraphs } from './TechnicalErrorParagraphs';
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { useTranslation } from 'react-i18next';
import type { ServiceResource } from '@/rtk/features/singleRights/singleRightsApi';
import { ErrorCode } from '@/resources/utils/errorCodeUtils';
import { useGetReporteeQuery } from '@/rtk/features/userInfoApi';
import { TechnicalErrorParagraphs } from '@/features/amUI/common/TechnicalErrorParagraphs/TechnicalErrorParagraphs';

export interface ResourceAlertProps {
/*** The resource */
resource: ServiceResource;
/*** The technical error if one has occured */
error?: { status: string; time: number } | null;
error?: { status: string; time: string } | null;
/*** The reasons why each right is not delegable */
rightReasons?: string[];
}
Expand All @@ -33,23 +34,10 @@ export const ResourceAlert = ({ resource, error, rightReasons }: ResourceAlertPr
} else if (error) {
headingText = t('delegation_modal.service_error.technical_error_heading');
content = (
<>
<Paragraph variant='long'>{t('delegation_modal.service_error.technical_error')}</Paragraph>
<Paragraph>
{t('delegation_modal.service_error.time_of_error', {
time: new Date(error.time).toLocaleDateString('nb-NO', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
}),
})}
</Paragraph>
<Paragraph>
{t('delegation_modal.service_error.error_status', {
status: error.status,
})}
</Paragraph>
</>
<TechnicalErrorParagraphs
status={error.status}
time={error.time}
/>
);
} else if (rightReasons) {
if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const ResourceInfo = ({ resource, toParty, onDelegate }: ResourceInfoProp
isDelegationCheckError && delegationCheckError && 'status' in delegationCheckError
? {
status: delegationCheckError.status.toString(),
time: delegationCheckError.data as number,
time: delegationCheckError.data as string,
}
: null;

Expand Down
73 changes: 73 additions & 0 deletions src/features/amUI/users/NewUserModal/NewOrgContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Button, TextField } from '@altinn/altinn-components';
import { useState } from 'react';
import { t } from 'i18next';
import { Paragraph } from '@digdir/designsystemet-react';

import { useGetOrganizationQuery } from '@/rtk/features/lookupApi';

import classes from './NewUserModal.module.css';
import { NewUserAlert } from './NewUserAlert';

export const NewOrgContent = () => {
const [orgNumber, setOrgNumber] = useState('');

const {
data: orgData,
isLoading,
error,
isError,
} = useGetOrganizationQuery(orgNumber, { skip: orgNumber.length !== 9 });

const errorDetails =
isError && error && 'status' in error
? {
status: error.status.toString(),
time: error.data as string,
}
: null;

const onAdd = () => {
if (orgData?.partyUuid) {
window.location.href = `${window.location.href}/${orgData?.partyUuid}`;
}
};

return (
<div className={classes.newOrgContent}>
{isError && (
<NewUserAlert
userType='org'
error={errorDetails}
/>
)}
<TextField
className={classes.textField}
label={t('common.org_number')}
size='sm'
onChange={(e) => setOrgNumber((e.target as HTMLInputElement).value.replace(/ /g, ''))}
/>
{orgData && (
<div className={classes.searchResult}>
<Paragraph>
<strong>{orgData.name}</strong>
</Paragraph>
<Paragraph size='sm'>
{t('common.org_nr')} {orgData.orgNumber}
{orgData.unitType === 'AAFY' ||
(orgData?.unitType === 'BEDR' && ' - ' + t('common.subunit'))}
</Paragraph>
</div>
)}

<div className={classes.validationButton}>
<Button
disabled={orgNumber.length !== 9 || isLoading || !orgData || isError}
loading={isLoading}
onClick={onAdd}
>
{t('new_user_modal.add_button')}
</Button>
</div>
</div>
);
};
7 changes: 6 additions & 1 deletion src/features/amUI/users/NewUserModal/NewPersonContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ export const NewPersonContent = () => {

return (
<div className={classes.newPersonContent}>
{isError && <NewUserAlert error={errorDetails} />}
{isError && (
<NewUserAlert
userType='person'
error={errorDetails}
/>
)}
<TextField
className={classes.textField}
label={t('common.ssn')}
Expand Down
39 changes: 14 additions & 25 deletions src/features/amUI/users/NewUserModal/NewUserAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,32 @@ import { Alert, Paragraph } from '@digdir/designsystemet-react';
import React from 'react';
import { useTranslation } from 'react-i18next';

import { TechnicalErrorParagraphs } from '../../common/TechnicalErrorParagraphs/TechnicalErrorParagraphs';

export interface NewUserAlertProps {
/*** The technical error if one has occured */
error?: { status: string; time: string } | null;
/*** The type of user to be added */
userType: 'person' | 'org';
}

export const NewUserAlert = ({ error }: NewUserAlertProps) => {
export const NewUserAlert = ({ error, userType }: NewUserAlertProps) => {
const { t } = useTranslation();
let errorText;

if (error && error.status === '404') {
errorText = <Paragraph size='sm'>{t('new_user_modal.not_found_error')}</Paragraph>;
if (error && error.status === '404' && userType === 'person') {
errorText = <Paragraph size='sm'>{t('new_user_modal.not_found_error_person')}</Paragraph>;
} else if (error && error.status === '400' && userType === 'org') {
errorText = <Paragraph size='sm'>{t('new_user_modal.not_found_error_person')}</Paragraph>;
} else if (error && error.status === '429') {
errorText = <Paragraph size='sm'>{t('new_user_modal.too_many_requests_error')}</Paragraph>;
} else if (error) {
errorText = (
<>
<Paragraph
size='sm'
variant='long'
>
{t('common.technical_error')}
</Paragraph>
<Paragraph size='sm'>
{t('common.time_of_error', {
time: new Date(error.time).toLocaleDateString('nb-NO', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
}),
})}
</Paragraph>
<Paragraph size='sm'>
{t('common.error_status', {
status: error.status,
})}
</Paragraph>
</>
<TechnicalErrorParagraphs
allinox marked this conversation as resolved.
Show resolved Hide resolved
status={error.status}
time={error.time}
size='sm'
/>
);
}

Expand Down
11 changes: 11 additions & 0 deletions src/features/amUI/users/NewUserModal/NewUserModal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@
flex-direction: column;
gap: 15px;
}

.newOrgContent {
padding-top: 5px;
display: flex;
flex-direction: column;
gap: 10px;
}

.searchResult {
padding: 10px 0 10px 0;
}
5 changes: 4 additions & 1 deletion src/features/amUI/users/NewUserModal/NewUserModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { t } from 'i18next';

import { NewPersonContent } from './NewPersonContent';
import classes from './NewUserModal.module.css';
import { NewOrgContent } from './NewOrgContent';

/**
* NewUserButton component renders a button that, when clicked, opens a modal to add a new user.
Expand Down Expand Up @@ -56,7 +57,9 @@ const NewUserModal: React.FC<NewUserModalProps> = ({ modalRef }) => {
<Tabs.Panel value='person'>
<NewPersonContent />
</Tabs.Panel>
<Tabs.Panel value='org'>Kommer senere...</Tabs.Panel>
<Tabs.Panel value='org'>
<NewOrgContent />
</Tabs.Panel>
</Tabs>
</Modal>
);
Expand Down
6 changes: 4 additions & 2 deletions src/localizations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
"time_of_error": "Error occurred at time: {{time}}",
"error_status": "Status: {{status}}",
"ssn": "National identity number",
"last_name": "Last name"
"last_name": "Last name",
"subunit": "Subunit"
},
"header": {
"menu-label": "Menu",
Expand Down Expand Up @@ -277,7 +278,8 @@
}
},
"new_user_modal": {
"not_found_error": "Did not find a user with the given name and national identity number. Please check that you entered the correct information.",
"not_found_error_person": "Did not find a user with the given name and national identity number. Please check that you entered the correct information.",
"not_found_error_org": "Did not find an organization with the given number. Please check that you entered the correct information.",
"too_many_requests_error": "You have been blocked due to too many failing attempts. Try again in an hour.",
"person": "Person",
"organization": "Organization",
Expand Down
Loading
Loading