Skip to content

Commit

Permalink
prepare for loading access packages through json file
Browse files Browse the repository at this point in the history
  • Loading branch information
mgunnerud committed Dec 12, 2024
1 parent cde8d18 commit b2cca3a
Show file tree
Hide file tree
Showing 20 changed files with 228 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#nullable enable

namespace Altinn.Studio.Designer.Models
namespace PolicyAdmin.Models
{
public class AccessPackageArea
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
#nullable enable

using System.Collections.Generic;

namespace Altinn.Studio.Designer.Models
namespace PolicyAdmin.Models
{
public class AccessPackage
public class AccessPackageOption
{
public required string Id { get; set; }

Expand All @@ -17,7 +15,5 @@ public class AccessPackage
public List<AccessPackageTag> Tags { get; set; } = [];

public AccessPackageArea Area { get; set; }

Check warning on line 17 in backend/PolicyAdmin/Models/AccessPackageOption.cs

View workflow job for this annotation

GitHub Actions / Run integration tests against actual gitea and db

Non-nullable property 'Area' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 17 in backend/PolicyAdmin/Models/AccessPackageOption.cs

View workflow job for this annotation

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

Non-nullable property 'Area' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 17 in backend/PolicyAdmin/Models/AccessPackageOption.cs

View workflow job for this annotation

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

Non-nullable property 'Area' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 17 in backend/PolicyAdmin/Models/AccessPackageOption.cs

View workflow job for this annotation

GitHub Actions / Analyze

Non-nullable property 'Area' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public List<AccessPackageService> Services { get; set; } = [];
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#nullable enable

namespace Altinn.Studio.Designer.Models
namespace PolicyAdmin.Models
{
public class AccessPackageTag
{
Expand Down
8 changes: 8 additions & 0 deletions backend/src/Designer/Controllers/PolicyController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ public async Task<ActionResult> GetActionOptions(string org, string app, Cancell
return Ok(actionOptions);
}

[HttpGet]
[Route("accesspackageoptions")]
public async Task<ActionResult> GetAccessPackageOptions(string org, string app, CancellationToken cancellationToken)
{
List<AccessPackageOption> accessPackageOptions = await _policyOptions.GetAccessPackageOptions(cancellationToken);
return Ok(accessPackageOptions);
}


private ValidationProblemDetails ValidatePolicy(ResourcePolicy policy)
{
Expand Down
70 changes: 68 additions & 2 deletions backend/src/Designer/Controllers/ResourceAdminController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using PolicyAdmin.Models;
using RepositoryModel = Altinn.Studio.Designer.RepositoryClient.Model.Repository;

namespace Altinn.Studio.Designer.Controllers
Expand Down Expand Up @@ -604,7 +605,7 @@ public async Task<ActionResult> GetAccessPackages(string org, CancellationToken
Name = "Bransjespesifikke"
};

List<AccessPackage> accessPackages =
List<AccessPackageOption> accessPackages =
[
new()
{
Expand Down Expand Up @@ -962,6 +963,7 @@ public async Task<ActionResult> GetAccessPackages(string org, CancellationToken
}
});

/*
// 3. GET full list of resources
List<ServiceResource> environmentResources = await _resourceRegistry.GetResourceList(env, false);
Expand Down Expand Up @@ -989,10 +991,74 @@ public async Task<ActionResult> GetAccessPackages(string org, CancellationToken
});
});

*/
return Ok(accessPackages);
}

[HttpGet]
[Route("designer/api/accesspackageservices/{accesspackage}/{env}")]
public async Task<ActionResult<List<AccessPackageService>>> GetServicesForAccessPackage(string org, string accesspackage, string env)
{
// 2. POST to get all resources per access package
List<SubjectResources> subjectResources = await _resourceRegistry.GetSubjectResources([accesspackage], env);

// start test data
subjectResources.Add(new SubjectResources()
{
Subject = new AttributeMatchV2()
{
Type = "",
Value = "",
Urn = "urn:altinn:accesspackage:akvakultur"
},
Resources = new List<AttributeMatchV2>() {
new AttributeMatchV2() {
Type = "",
Value = "innsyn-i-driftsplaner-for-akvakulturanlegg-i-sj-vann",
Urn = ""
},
new AttributeMatchV2() {
Type = "",
Value = "ske-innrapportering-omsetning-raafisk",
Urn = ""
},
new AttributeMatchV2() {
Type = "",
Value = "mat-maskinportenschema-lakselusrapportering",
Urn = ""
},
}
});
// end test data

// 3. GET full list of resources
List<ServiceResource> environmentResources = await _resourceRegistry.GetResourceList(env, false, true);
List<AttributeMatchV2> resources = subjectResources.Find(x => x.Subject.Urn == accesspackage)?.Resources;

OrgList orgList = await GetOrgList();
List<AccessPackageService> result = [];

resources?.ForEach(resourceMatch =>
{
ServiceResource fullResource = environmentResources.Find(x => x.Identifier == resourceMatch.Value);

if (fullResource != null)
{
orgList.Orgs.TryGetValue(fullResource.HasCompetentAuthority.Orgcode.ToLower(), out Org organization);

result.Add(new AccessPackageService()
{
Identifier = resourceMatch.Value,
Title = fullResource?.Title,
HasCompetentAuthority = fullResource.HasCompetentAuthority,
LogoUrl = organization.Logo
});
}
});

return result;
}

[HttpGet]
[Route("designer/api/{org}/resources/altinn2linkservices/{env}")]
public async Task<ActionResult<List<AvailableService>>> GetAltinn2LinkServices(string org, string env)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,19 +228,19 @@ public async Task<List<ServiceResource>> GetResources(string env)
/// Get resource list
/// </summary>
/// <returns>List of all resources</returns>
public async Task<List<ServiceResource>> GetResourceList(string env, bool includeAltinn2)
public async Task<List<ServiceResource>> GetResourceList(string env, bool includeAltinn2, bool includeApps = false)
{

string endpointUrl;

//Checks if not tested locally by passing dev as env parameter
if (!env.ToLower().Equals("dev"))
{
endpointUrl = $"{GetResourceRegistryBaseUrl(env)}{_platformSettings.ResourceRegistryUrl}/resourcelist/?includeApps=false&includeAltinn2={includeAltinn2}";
endpointUrl = $"{GetResourceRegistryBaseUrl(env)}{_platformSettings.ResourceRegistryUrl}/resourcelist/?includeApps={includeApps}&includeAltinn2={includeAltinn2}";
}
else
{
endpointUrl = $"{_platformSettings.ResourceRegistryDefaultBaseUrl}{_platformSettings.ResourceRegistryUrl}/resourcelist/?includeApps=false&includeAltinn2={includeAltinn2}";
endpointUrl = $"{_platformSettings.ResourceRegistryDefaultBaseUrl}{_platformSettings.ResourceRegistryUrl}/resourcelist/?includeApps={includeApps}&includeAltinn2={includeAltinn2}";
}

JsonSerializerOptions options = new JsonSerializerOptions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public interface IResourceRegistry
/// Integration point for retrieving the full list of resources
/// </summary>
/// <returns>The resource full list of all resources if exists</returns>
Task<List<ServiceResource>> GetResourceList(string env, bool includeAltinn2);
Task<List<ServiceResource>> GetResourceList(string env, bool includeAltinn2, bool includeApps = false);

/// <summary>
/// Get Resource from Altinn 2 service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ public interface IPolicyOptions
public Task<List<ActionOption>> GetActionOptions(CancellationToken cancellationToken = default);

public Task<List<SubjectOption>> GetSubjectOptions(CancellationToken cancellationToken = default);

public Task<List<AccessPackageOption>> GetAccessPackageOptions(CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ public PolicyOptionsClient(HttpClient httpClient, ILogger<PolicyOptionsClient> l
_logger = logger;
}

public async Task<List<AccessPackageOption>> GetAccessPackageOptions(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Temp location. Will be moved to CDN
string url = "https://raw.githubusercontent.com/Altinn/altinn-studio-docs/master/content/authorization/architecture/resourceregistry/accesspackageoptions.json";

List<AccessPackageOption> accessPackageOptions;

try
{
HttpResponseMessage response = await _client.GetAsync(url, cancellationToken);
string accessPackageOptionsString = await response.Content.ReadAsStringAsync(cancellationToken);
accessPackageOptions = System.Text.Json.JsonSerializer.Deserialize<List<AccessPackageOption>>(accessPackageOptionsString);
return accessPackageOptions;
}
catch (Exception ex)
{
throw new Exception($"Something went wrong when retrieving Action options", ex);
}
}

public async Task<List<ActionOption>> GetActionOptions(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';
import React, { useState } from 'react';
import { Paragraph } from '@digdir/designsystemet-react';
import { useTranslation } from 'react-i18next';
import classes from './PolicyAccessPackageAccordion.module.css';
import type { PolicyAccessPackage } from '@altinn/policy-editor/types';
import { Paragraph } from '@digdir/designsystemet-react';
import { PolicyAccordion } from '../PolicyAccordion/PolicyAccordion';
import { useResourceAccessPackageServicesQuery } from 'app-shared/hooks/queries/useResourceAccessPackageServicesQuery';
import { StudioSpinner } from '@studio/components';

interface PolicyAccessPackageAccordionProps {
accessPackage: PolicyAccessPackage;
Expand All @@ -17,20 +19,33 @@ export const PolicyAccessPackageAccordion = ({
selectPackageElement,
}: PolicyAccessPackageAccordionProps): React.ReactElement => {
const { t } = useTranslation();
const [isServicesEnabled, setIsServicesEnabled] = useState<boolean>(false);

const { data: services, isLoading } = useResourceAccessPackageServicesQuery(
accessPackage.urn,
localStorage.getItem('policyEditorAccessPackageEnv') || 'prod', // hardcoded to prod for now
isServicesEnabled,
);

const onOpenAccordion = () => {
setIsServicesEnabled(true);
};

return (
<div className={classes.accessPackageAccordion}>
<PolicyAccordion
title={accessPackage.name}
subTitle={accessPackage.description}
extraHeaderContent={selectPackageElement}
onOpened={onOpenAccordion}
>
{accessPackage.services.length > 0 ? (
{isLoading && <StudioSpinner spinnerTitle='Laster tjenester...' />}
{services?.length > 0 && (
<>
<div className={classes.serviceContainerHeader}>
{t('policy_editor.access_package_services')}
</div>
{accessPackage.services.map((resource) => {
{services.map((resource) => {
return (
<div key={resource.identifier} className={classes.serviceContainer}>
{resource.logoUrl ? (
Expand All @@ -49,7 +64,8 @@ export const PolicyAccessPackageAccordion = ({
);
})}
</>
) : (
)}
{services?.length === 0 && (
<Paragraph size='xs'>{t('policy_editor.access_package_no_services')}</Paragraph>
)}
</PolicyAccordion>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Paragraph, Alert, CheckboxGroup, Checkbox } from '@digdir/designsystemet-react';
import { StudioLabelAsParagraph } from '@studio/components';
import type { PolicyAccessPackage } from '../../../../types';
import { StudioLabelAsParagraph, StudioTextfield } from '@studio/components';
import type { PolicyAccessPackage, PolicyAccessPackageArea } from '../../../../types';
import { getUpdatedRules } from '../../../../utils/PolicyRuleUtils';
import { usePolicyEditorContext } from '../../../../contexts/PolicyEditorContext';
import { usePolicyRuleContext } from '../../../../contexts/PolicyRuleContext';
Expand All @@ -16,6 +16,7 @@ const selectedLanguage = 'nb';

export const PolicyAccessPackages = (): React.ReactElement => {
const { t } = useTranslation();
const [searchValue, setSearchValue] = useState<string>('');
const { policyRules, accessPackages, setPolicyRules, savePolicy } = usePolicyEditorContext();
const { policyRule } = usePolicyRuleContext();

Expand Down Expand Up @@ -64,6 +65,42 @@ export const PolicyAccessPackages = (): React.ReactElement => {
savePolicy(updatedRules);
};

const handleSearch = (search: string) => {
setSearchValue(search);
};

const isStringMatch = (matchString: string) => {
return matchString.toLowerCase().includes(searchValue.toLowerCase());
};

const accessPackagesToRender = groupedAccessPackagesByArea.reduce(
(
areas: {
area: PolicyAccessPackageArea;
packages: PolicyAccessPackage[];
}[],
area,
): {
area: PolicyAccessPackageArea;
packages: PolicyAccessPackage[];
}[] => {
const matchingPackages = area.packages.filter(
(pack) =>
!searchValue ||
isStringMatch(pack.name) ||
isStringMatch(pack.description) ||
isStringMatch(pack.area.name) ||
isStringMatch(pack.area.description),
);
const returnAreas = [...areas];
if (matchingPackages.length > 0) {
returnAreas.push({ ...area, packages: matchingPackages });
}
return returnAreas;
},
[],
);

const renderAccessPackageAccordion = (accessPackage: PolicyAccessPackage): React.ReactNode => {
const isChecked = chosenAccessPackages.includes(accessPackage.urn);
const checkboxLabel = t(
Expand Down Expand Up @@ -103,6 +140,12 @@ export const PolicyAccessPackages = (): React.ReactElement => {
<StudioLabelAsParagraph size='sm' spacing>
{t('policy_editor.access_package_header')}
</StudioLabelAsParagraph>
<StudioTextfield
label='Søk'
size='small'
value={searchValue}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => handleSearch(event.target.value)}
/>
{chosenAccessPackages.length > 0 && (
<>
<StudioLabelAsParagraph size='xs' spacing>
Expand All @@ -116,13 +159,14 @@ export const PolicyAccessPackages = (): React.ReactElement => {
<StudioLabelAsParagraph size='xs' spacing>
{t('policy_editor.access_package_all_packages')}
</StudioLabelAsParagraph>
{groupedAccessPackagesByArea.map(({ area, packages }) => {
{accessPackagesToRender.map(({ area, packages }) => {
return (
<PolicyAccordion
key={area.id}
key={`${searchValue}-${area.id}`}
icon={area.iconName}
title={area.name}
subTitle={area.shortDescription}
defaultOpen={!!searchValue}
>
{packages.map(renderAccessPackageAccordion)}
</PolicyAccordion>
Expand Down
Loading

0 comments on commit b2cca3a

Please sign in to comment.