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

Feature/10670 import service from altinn 2 #11384

Merged
31 commits merged into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
0456634
adding basic frontend logic
wrt95 Oct 16, 2023
e8d8f07
spll check
TheTechArch Oct 16, 2023
5320b4c
typofix
TheTechArch Oct 16, 2023
a37d497
Fixed spelling
TheTechArch Oct 16, 2023
e2536e1
Adding style
wrt95 Oct 17, 2023
0fedcff
Fixed ttd handling
TheTechArch Oct 18, 2023
bb4cbb5
Adding handling for service
wrt95 Oct 18, 2023
5e261e6
Merge branch 'feature/10670-import-service-from-Altinn-2' of https://…
wrt95 Oct 18, 2023
5f10eb9
fixing code
wrt95 Oct 18, 2023
0ad0bb2
Fixed cache key
TheTechArch Oct 18, 2023
ebb2bc3
Changed to post for import
TheTechArch Oct 18, 2023
66c19d5
Trying to set up import
wrt95 Oct 18, 2023
880b5d8
Merge branch 'feature/10670-import-service-from-Altinn-2' of https://…
wrt95 Oct 18, 2023
f2639ef
Trying to fix undefined
wrt95 Oct 18, 2023
13ac020
fixing undefined
wrt95 Oct 18, 2023
52602c7
updating post
wrt95 Oct 18, 2023
b8c75de
Cleaning up cod
wrt95 Oct 18, 2023
395e27a
Adding tests
wrt95 Oct 18, 2023
a4d40d8
fixing queriesMock
wrt95 Oct 18, 2023
f84ed59
rollback useGetResourceList
wrt95 Oct 18, 2023
4b9489c
dotnet format
TheTechArch Oct 19, 2023
afab94a
Fixed method
TheTechArch Oct 19, 2023
f39841f
Fixing modal z index and SetupTab
wrt95 Oct 23, 2023
1df3e6a
Merge branch 'master' of https://github.com/Altinn/altinn-studio
wrt95 Oct 23, 2023
238b87f
Merge branch 'master' of https://github.com/Altinn/altinn-studio
wrt95 Oct 24, 2023
81b4102
Merge branch 'master' into feature/10670-import-service-from-Altinn-2
wrt95 Oct 24, 2023
a2f4a2b
Fixing feedback fromPR
wrt95 Oct 24, 2023
67e58a3
Merge branch 'feature/10670-import-service-from-Altinn-2' of https://…
wrt95 Oct 24, 2023
fc0e87e
Adding ServerCode enum
wrt95 Oct 24, 2023
3a3fdf4
Feedback. Added util fo rcommmon org code
TheTechArch Oct 24, 2023
daa76b2
Merge branch 'feature/10670-import-service-from-Altinn-2' of https://…
TheTechArch Oct 24, 2023
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
18 changes: 13 additions & 5 deletions backend/src/Designer/Controllers/ResourceAdminController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
return _repository.AddServiceResource(org, resource);
}

[HttpGet]
[HttpPost]
[Route("designer/api/{org}/resources/importresource/{serviceCode}/{serviceEdition}/{environment}")]
public async Task<ActionResult> ImportResource(string org, string serviceCode, int serviceEdition, string environment)
{
Expand Down Expand Up @@ -260,19 +260,27 @@

[HttpGet]
[Route("designer/api/{org}/resources/altinn2linkservices/{environment}")]
public async Task<ActionResult<List<AvailableService>>> GetAltinn2LinkServices(string org, string enviroment)
public async Task<ActionResult<List<AvailableService>>> GetAltinn2LinkServices(string org, string environment)
{
string cacheKey = "availablelinkservices:" + org;
string cacheKey = "availablelinkservices:" + org+environment;
if (!_memoryCache.TryGetValue(cacheKey, out List<AvailableService> linkServices))
{

List<AvailableService> unfiltered = await _altinn2MetadataClient.AvailableServices(1044, enviroment);
List<AvailableService> unfiltered = await _altinn2MetadataClient.AvailableServices(1044, environment);

var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.High)
.SetAbsoluteExpiration(new TimeSpan(0, _cacheSettings.DataNorgeApiCacheTimeout, 0));

linkServices = unfiltered.Where(a => a.ServiceType.Equals(ServiceType.Link) && a.ServiceOwnerCode.ToLower().Equals(org.ToLower())).ToList();
if(org.ToLower().Equals("ttd"))
{
linkServices = unfiltered.Where(a => a.ServiceType.Equals(ServiceType.Link) && (a.ServiceOwnerCode.ToLower().Equals(org.ToLower()) || a.ServiceOwnerCode.ToLower().Equals("acn"))).ToList();
}
else
{
linkServices = unfiltered.Where(a => a.ServiceType.Equals(ServiceType.Link) && a.ServiceOwnerCode.ToLower().Equals(org.ToLower())).ToList();
}
Fixed Show fixed Hide fixed

_memoryCache.Set(cacheKey, linkServices, cacheEntryOptions);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@

public async Task<List<AvailableService>> AvailableServices(int languageId, string environment)
{
List<AvailableService>? availableServices = null;

Check warning on line 56 in backend/src/Designer/TypedHttpClients/Altinn2Metadata/Altinn2MetadataClient.cs

View workflow job for this annotation

GitHub Actions / Run dotnet build and test (ubuntu-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 56 in backend/src/Designer/TypedHttpClients/Altinn2Metadata/Altinn2MetadataClient.cs

View workflow job for this annotation

GitHub Actions / Run integration tests against actual gitea

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

Check warning on line 56 in backend/src/Designer/TypedHttpClients/Altinn2Metadata/Altinn2MetadataClient.cs

View workflow job for this annotation

GitHub Actions / Run dotnet build and test (macos-latest)

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.
string bridgeBaseUrl = GetSblBridgeUrl(environment);
string availabbleServicePath = $"h{bridgeBaseUrl}metadata/api/availableServices?languageID={languageId}&appTypesToInclude=0&includeExpired=false";
string availabbleServicePath = $"{bridgeBaseUrl}metadata/api/availableServices?languageID={languageId}&appTypesToInclude=0&includeExpired=false";

HttpResponseMessage response = await _httpClient.GetAsync(availabbleServicePath);

Expand All @@ -70,7 +70,7 @@

private string GetSblBridgeUrl(string environment)
{
if (!_rrs.TryGetValue(environment, out ResourceRegistryEnvironmentSettings envSettings))
if (!_rrs.TryGetValue(environment.ToLower(), out ResourceRegistryEnvironmentSettings envSettings))
{
throw new ArgumentException($"Invalid environment. Missing environment config for {environment}");
}
Expand Down
4 changes: 3 additions & 1 deletion frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,8 @@
"resourceadm.deploy_version_upload_button": "Last opp dine endringer",
"resourceadm.error_back_to_dashboard": "Gå tilbake til dashbord",
"resourceadm.error_page_text": "Du har nådd en ugyldig adresse",
"resourceadm.import_resource_empty_list": "Det finnes ingen servicer i miljøet du valgte",
framitdavid marked this conversation as resolved.
Show resolved Hide resolved
"resourceadm.import_resource_spinner": "Importerer ressursen",
"resourceadm.left_nav_bar_about": "Om ressursen",
"resourceadm.left_nav_bar_back": "Tilbake til dashbord",
"resourceadm.left_nav_bar_back_icon": "Tilbake til dashbord",
Expand Down Expand Up @@ -1489,4 +1491,4 @@
"validation_errors.pattern": "Feil format eller verdi",
"validation_errors.required": "Feltet er påkrevd",
"validation_errors.value_as_url": "Ugyldig lenke"
}
}
2 changes: 2 additions & 0 deletions frontend/packages/shared/src/api/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
publishResourcePath,
appMetadataPath,
serviceConfigPath,
importResourceFromAltinn2Path,
} from 'app-shared/api/paths';
import { AddLanguagePayload } from 'app-shared/types/api/AddLanguagePayload';
import { AddRepoParams } from 'app-shared/types/api';
Expand Down Expand Up @@ -94,3 +95,4 @@ export const updatePolicy = (org: string, repo: string, id: string, payload: Pol
export const createResource = (org: string, payload: NewResource) => post(resourceCreatePath(org), payload);
export const updateResource = (org: string, repo: string, payload: Resource) => put(resourceEditPath(org, repo), payload);
export const publishResource = (org: string, repo: string, id: string, env: string) => post(publishResourcePath(org, repo, id, env), { headers: { 'Content-Type': 'application/json' } });
export const importResourceFromAltinn2 = (org: string, environment: string, serviceCode: string, serviceEdition: string) => post<Resource>(importResourceFromAltinn2Path(org, environment, serviceCode, serviceEdition));
2 changes: 2 additions & 0 deletions frontend/packages/shared/src/api/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ export const resourceEditPath = (org, id) => `${basePath}/${org}/resources/updat
export const resourceValidatePolicyPath = (org, repo, id) => `${basePath}/${org}/${repo}/policy/validate/${id}`; // Get
export const resourceValidateResourcePath = (org, repo, id) => `${basePath}/${org}/resources/validate/${repo}/${id}`; // Get
export const publishResourcePath = (org, repo, id, env) => `${basePath}/${org}/resources/publish/${repo}/${id}?env=${env}`; // Get
export const altinn2LinkServicesPath = (org, env) => `${basePath}/${org}/resources/altinn2linkservices/${env}`; // Get
export const importResourceFromAltinn2Path = (org, env, serviceCode, serviceEdition) => `${basePath}/${org}/resources/importresource/${serviceCode}/${serviceEdition}/${env}`; // Post

// Process Editor
export const processEditorPath = (org, repo) => `${basePath}/${org}/${repo}/process-modelling/process-definition`;
3 changes: 3 additions & 0 deletions frontend/packages/shared/src/api/queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { get, put } from 'app-shared/utils/networking';
import {
altinn2LinkServicesPath,
appMetadataPath,
appPolicyPath,
branchStatusPath,
Expand Down Expand Up @@ -64,6 +65,7 @@ import type { Resource, ResourceListItem, ResourceVersionStatus, Validation } fr
import type { AppConfig } from 'app-shared/types/AppConfig';
import type { Commit } from 'app-shared/types/Commit';
import type { ApplicationMetadata } from 'app-shared/types/ApplicationMetadata';
import { Altinn2LinkService } from 'app-shared/types/Altinn2LinkService';

export const getAppReleases = (owner: string, app: string) => get<AppReleasesResponse>(releasesPath(owner, app, 'Descending'));
export const getBranchStatus = (owner: string, app: string, branch: string) => get<BranchStatus>(branchStatusPath(owner, app, branch));
Expand Down Expand Up @@ -115,6 +117,7 @@ export const getResourceList = (org: string) => get<ResourceListItem[]>(resource
export const getResource = (org: string, repo: string, id: string) => get<Resource>(resourceSinglePath(org, repo, id));
export const getValidatePolicy = (org: string, repo: string, id: string) => get<Validation>(resourceValidatePolicyPath(org, repo, id));
export const getValidateResource = (org: string, repo: string, id: string) => get<Validation>(resourceValidateResourcePath(org, repo, id));
export const getAltinn2LinkServices = (org: string, environment: string) => get<Altinn2LinkService[]>(altinn2LinkServicesPath(org, environment));

// ProcessEditor
export const getBpmnFile = (org: string, app: string) => get(processEditorPath(org, app));
Expand Down
2 changes: 2 additions & 0 deletions frontend/packages/shared/src/mocks/queriesMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,6 @@ export const queriesMock: ServicesContextProps = {
updateAppConfig: jest.fn(),
getRepoInitialCommit: jest.fn(),
publishResource: jest.fn(),
getAltinn2LinkServices: jest.fn(),
importResourceFromAltinn2: jest.fn(),
};
5 changes: 5 additions & 0 deletions frontend/packages/shared/src/types/Altinn2LinkService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface Altinn2LinkService {
serviceName: string;
externalServiceCode: string;
externalServiceEditionCode: string;
}
2 changes: 2 additions & 0 deletions frontend/packages/shared/src/types/QueryKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ export enum QueryKey {
ValidatePolicy = 'ValidatePolicy',
ValidateResource = 'ValidateResource',
PublishResource = 'PublishResource',
Altinn2Services = 'Altinn2Services',
ImportAltinn2Resource = 'ImportAltinn2Resource',
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@
margin-left: 10px;
}

.contentDivider {
border: solid 1px #d6d6d6;
margin-top: 25px;
margin-bottom: 15px;
width: 80%;
}

.contentWidth {
max-width: 600px;
}
Original file line number Diff line number Diff line change
@@ -1,45 +1,89 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { render as rtlRender, screen, waitForElementToBeRemoved } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ImportResourceModal, ImportResourceModalProps } from './ImportResourceModal'; // Update the import path
import { ImportResourceModal, ImportResourceModalProps } from './ImportResourceModal';
import { textMock } from '../../../testing/mocks/i18nMock';
import { act } from 'react-dom/test-utils';
import { MemoryRouter } from 'react-router-dom';
import { ServicesContextProps, ServicesContextProvider } from 'app-shared/contexts/ServicesContext';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { Altinn2LinkService } from 'app-shared/types/Altinn2LinkService';
import { useImportResourceFromAltinn2Mutation } from 'resourceadm/hooks/mutations';
import { UseMutationResult } from '@tanstack/react-query';
import { Resource } from 'app-shared/types/ResourceAdm';

describe('ImportResourceModal', () => {
const mockOnClose = jest.fn();
const mockAltinn2LinkService: Altinn2LinkService = {
externalServiceCode: 'code1',
externalServiceEditionCode: 'edition1',
serviceName: 'TestService',
};
const mockAltinn2LinkServices: Altinn2LinkService[] = [mockAltinn2LinkService];
const mockOption: string = `${mockAltinn2LinkService.externalServiceCode}-${mockAltinn2LinkService.externalServiceEditionCode}-${mockAltinn2LinkService.serviceName}`;

const defaultProps: ImportResourceModalProps = {
isOpen: true,
onClose: mockOnClose,
};
const mockOnClose = jest.fn();
const getAltinn2LinkServices = jest.fn().mockImplementation(() => Promise.resolve({}));

jest.mock('../../hooks/mutations/useImportResourceFromAltinn2Mutation');
const importResourceFromAltinn2 = jest.fn();
const mockImportResourceFromAltinn2 = useImportResourceFromAltinn2Mutation as jest.MockedFunction<
typeof useImportResourceFromAltinn2Mutation
>;
mockImportResourceFromAltinn2.mockReturnValue({
mutate: importResourceFromAltinn2,
} as unknown as UseMutationResult<
Resource,
unknown,
{
environment: string;
serviceCode: string;
serviceEdition: string;
},
unknown
>);

const defaultProps: ImportResourceModalProps = {
isOpen: true,
onClose: mockOnClose,
};

describe('ImportResourceModal', () => {
afterEach(jest.clearAllMocks);

it('selects environment and service, then checks if import button exists', async () => {
const user = userEvent.setup();

render(<ImportResourceModal {...defaultProps} />);
render();

const importButtonText = textMock('resourceadm.dashboard_import_modal_import_button');
const importButton = screen.queryByRole('button', { name: importButtonText });
expect(importButton).not.toBeInTheDocument();

const [, environmentSelect] = screen.getAllByLabelText(textMock('resourceadm.dashboard_import_modal_select_env'));
const [, environmentSelect] = screen.getAllByLabelText(
textMock('resourceadm.dashboard_import_modal_select_env'),
);
await act(() => user.click(environmentSelect));
await act(() => user.click(screen.getByRole('option', { name: 'AT21' })))
await act(() => user.click(screen.getByRole('option', { name: 'AT21' })));

expect(environmentSelect).toHaveValue('AT21');
expect(importButton).not.toBeInTheDocument();

const [, serviceSelect] = screen.getAllByLabelText(textMock('resourceadm.dashboard_import_modal_select_service'));
await waitForElementToBeRemoved(() =>
screen.queryByTitle(textMock('resourceadm.import_resource_spinner')),
);

const [, serviceSelect] = screen.getAllByLabelText(
textMock('resourceadm.dashboard_import_modal_select_service'),
);
await act(() => user.click(serviceSelect));
await act(() => user.click(screen.getByRole('option', { name: 'Service1' })))
await act(() => user.click(screen.getByRole('option', { name: mockOption })));

expect(serviceSelect).toHaveValue('Service1');
expect(serviceSelect).toHaveValue(mockOption);
expect(screen.getByRole('button', { name: importButtonText })).toBeInTheDocument();
});

it('calls onClose function when close button is clicked', async () => {
const user = userEvent.setup();
render(<ImportResourceModal {...defaultProps} />);
render();

const closeButton = screen.getByRole('button', { name: textMock('general.cancel') });
await act(() => user.click(closeButton));
Expand All @@ -48,8 +92,52 @@ describe('ImportResourceModal', () => {
});

it('should be closed by default', () => {
render(<ImportResourceModal isOpen={false} onClose={() => {}} />);
render({ isOpen: false });

const closeButton = screen.queryByRole('button', { name: textMock('general.cancel') });
expect(closeButton).not.toBeInTheDocument();
});

it('calls import resource from Altinn 2 when import is clicked', async () => {
const user = userEvent.setup();
render();

const [, environmentSelect] = screen.getAllByLabelText(
textMock('resourceadm.dashboard_import_modal_select_env'),
);
await act(() => user.click(environmentSelect));
await act(() => user.click(screen.getByRole('option', { name: 'AT21' })));
await waitForElementToBeRemoved(() =>
screen.queryByTitle(textMock('resourceadm.import_resource_spinner')),
);
const [, serviceSelect] = screen.getAllByLabelText(
textMock('resourceadm.dashboard_import_modal_select_service'),
);
await act(() => user.click(serviceSelect));
await act(() => user.click(screen.getByRole('option', { name: mockOption })));

const importButton = screen.getByRole('button', {
name: textMock('resourceadm.dashboard_import_modal_import_button'),
});
await act(() => user.click(importButton));

expect(importResourceFromAltinn2).toHaveBeenCalledTimes(1);
});
});

const render = (props: Partial<ImportResourceModalProps> = {}) => {
getAltinn2LinkServices.mockImplementation(() => Promise.resolve(mockAltinn2LinkServices));

const allQueries: ServicesContextProps = {
...queriesMock,
getAltinn2LinkServices,
};

return rtlRender(
<MemoryRouter>
<ServicesContextProvider {...allQueries} client={createQueryClientMock()}>
<ImportResourceModal {...defaultProps} {...props} />
</ServicesContextProvider>
</MemoryRouter>,
);
};
Loading
Loading