From 132c64650dbb0ea683969086215ddd1d362a85d3 Mon Sep 17 00:00:00 2001 From: ebelegu Date: Wed, 30 Aug 2023 06:48:31 +0200 Subject: [PATCH 01/25] Bruker useSWR for api-kall --- package-lock.json | 46 +++++++ package.json | 1 + .../Arbeidsforholdboks/Arbeidsforholdboks.tsx | 31 ++--- .../ForebyggeFrav\303\246rboks.tsx" | 66 ++++----- .../Kandidatlisteboks/Kandidatlisteboks.tsx | 47 +++---- .../Tiltakboks/Tiltakboks.tsx | 126 ++++++++---------- src/api/arbeidsavtalerApi.ts | 13 -- src/api/arbeidsforholdApi.ts | 35 ----- src/api/presenterteKandidaterApi.ts | 29 ---- "src/api/sykefrav\303\246rStatistikkApi.ts" | 27 ---- src/api/useAntallArbeidsforholdFraAareg.ts | 42 ++++++ src/api/useAntallKandidater.ts | 32 +++++ src/api/useArbeidsavtaler.ts | 35 +++++ "src/api/useSykefrav\303\246r.ts" | 33 +++++ 14 files changed, 314 insertions(+), 249 deletions(-) delete mode 100644 src/api/arbeidsavtalerApi.ts delete mode 100644 src/api/arbeidsforholdApi.ts delete mode 100644 src/api/presenterteKandidaterApi.ts delete mode 100644 "src/api/sykefrav\303\246rStatistikkApi.ts" create mode 100644 src/api/useAntallArbeidsforholdFraAareg.ts create mode 100644 src/api/useAntallKandidater.ts create mode 100644 src/api/useArbeidsavtaler.ts create mode 100644 "src/api/useSykefrav\303\246r.ts" diff --git a/package-lock.json b/package-lock.json index 56d077c97..f8c999f0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "react-tooltip": "^4.2.13", "regenerator-runtime": "^0.13.7", "smoothscroll-polyfill": "^0.4.4", + "swr": "^2.2.2", "typescript": "4.5.4", "uuid": "^9.0.0", "whatwg-fetch": "^3.6.2", @@ -7292,6 +7293,11 @@ "node": ">= 10" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -18606,6 +18612,18 @@ "tslib": "^2.0.3" } }, + "node_modules/swr": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.2.tgz", + "integrity": "sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -19331,6 +19349,14 @@ "braces": "^3.0.2" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -25705,6 +25731,11 @@ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, + "client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -33886,6 +33917,15 @@ "tslib": "^2.0.3" } }, + "swr": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.2.tgz", + "integrity": "sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==", + "requires": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + } + }, "symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -34406,6 +34446,12 @@ "braces": "^3.0.2" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 530a857c6..3f80264d2 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-tooltip": "^4.2.13", "regenerator-runtime": "^0.13.7", "smoothscroll-polyfill": "^0.4.4", + "swr": "^2.2.2", "typescript": "4.5.4", "uuid": "^9.0.0", "whatwg-fetch": "^3.6.2", diff --git a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx index f81c0f01d..9085a3747 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx @@ -1,25 +1,20 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React from 'react'; +import * as Sentry from '@sentry/browser'; import { innsynAaregURL } from '../../../../lenker'; import arbeidsforholdikon from './arbeidsforholdikon.svg'; -import { hentAntallArbeidsforholdFraAareg } from '../../../../api/arbeidsforholdApi'; -import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import { useAntallArbeidsforholdFraAareg } from '../../../../api/useAntallArbeidsforholdFraAareg'; import './ArbeidsforholdBoks.css'; import { Tjenesteboks } from '../Tjenesteboks'; const Arbeidsforholdboks = () => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - const [antallArbeidsforhold, setAntallArbeidsforhold] = useState('–'); - useEffect(() => { - if (valgtOrganisasjon) - hentAntallArbeidsforholdFraAareg( - valgtOrganisasjon.organisasjon.OrganizationNumber, - valgtOrganisasjon.organisasjon.ParentOrganizationNumber ?? '' - ).then((antallArbeidsforholdRespons) => - setAntallArbeidsforhold( - antallArbeidsforholdRespons > 0 ? antallArbeidsforholdRespons.toString() : '–' - ) - ); - }, [valgtOrganisasjon]); + const { data, error } = useAntallArbeidsforholdFraAareg(); + + if (error !== undefined) { + return null; + } + + const antallArbeidsforhold = data?.second ?? 0; + const orgnummerFraUrl = new URLSearchParams(window.location.search).get('bedrift') ?? ''; const href = innsynAaregURL + (orgnummerFraUrl === '' ? '' : `?bedrift=${orgnummerFraUrl}`); @@ -33,7 +28,9 @@ const Arbeidsforholdboks = () => {
{' '} - {antallArbeidsforhold} + + {antallArbeidsforhold > 0 ? antallArbeidsforhold : '-'} + arbeidsforhold (aktive og avsluttede){' '}
diff --git "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" index a48757609..3a12c0dc4 100644 --- "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" +++ "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" @@ -1,12 +1,10 @@ -import {lenkeTilForebyggefravar} from '../../../../lenker'; -import React, {useContext, useEffect, useState} from 'react'; -import * as Sentry from "@sentry/browser"; +import { lenkeTilForebyggefravar } from '../../../../lenker'; +import React from 'react'; +import * as Sentry from '@sentry/browser'; import ForebyggeFraværIkon from './ForebyggeFraværIkon.svg'; import './ForebyggeFraværboks.css'; -import {OrganisasjonsDetaljerContext} from '../../../OrganisasjonDetaljerProvider'; -import {hentSykefravær, Sykefraværsrespons} from '../../../../api/sykefraværStatistikkApi'; -import {StortTall, Tjenesteboks} from "../Tjenesteboks"; - +import { useSykefravær } from '../../../../api/useSykefravær'; +import { StortTall, Tjenesteboks } from '../Tjenesteboks'; const ForebyggeFraværboks = () => { const valgtbedrift = () => { @@ -14,52 +12,46 @@ const ForebyggeFraværboks = () => { return orgnummerFraUrl === '' ? '' : `?bedrift=${orgnummerFraUrl}`; }; - return - - ; + return ( + + + + ); }; -const beskrivelse = 'Verktøy for å forebygge fravær i din virksomhet.' +const beskrivelse = 'Verktøy for å forebygge fravær i din virksomhet.'; const Beskrivelse = () => { - const {valgtOrganisasjon} = useContext(OrganisasjonsDetaljerContext); - const [sykefravær, setSykefravær] = useState(undefined); - const statistikktype = (type: string) => { switch (type) { case 'NÆRING': case 'BRANSJE': - return 'bransje' - default : - return 'bedrift' + return 'bransje'; + default: + return 'bedrift'; } - } - useEffect(() => { - if (valgtOrganisasjon) - hentSykefravær(valgtOrganisasjon.organisasjon.OrganizationNumber).then(sykefraværsrespons => - setSykefravær(sykefraværsrespons), - ).catch(error => { - Sentry.captureException(error) - setSykefravær(undefined); - }); - }, [valgtOrganisasjon]); + }; + + const { data: sykefravær } = useSykefravær(); if (sykefravær !== undefined) { return ( - - {sykefravær.prosent.toString()} % - - <> legemeldt sykefravær i din {statistikktype(sykefravær.type)}. Lag en plan for å redusere fraværet. + {sykefravær.prosent.toString()} % + <> + {' '} + legemeldt sykefravær i din {statistikktype(sykefravær.type)}. Lag en plan for å + redusere fraværet.{' '} + ); } return {beskrivelse}; -} +}; export default ForebyggeFraværboks; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx index 3c0cbf1e7..25e50c381 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx @@ -1,36 +1,37 @@ -import React, {useContext, useEffect, useState} from 'react'; -import {OrganisasjonsDetaljerContext} from '../../../OrganisasjonDetaljerProvider'; -import {kandidatlisteURL} from '../../../../lenker'; -import {Tjenesteboks} from "../Tjenesteboks"; -import {hentAntallKandidater} from "../../../../api/presenterteKandidaterApi"; -import ikon from "./kandidatlisteboks-ikon.svg"; +import React from 'react'; +import { kandidatlisteURL } from '../../../../lenker'; +import { Tjenesteboks } from '../Tjenesteboks'; +import { useAntallKandidater } from '../../../../api/useAntallKandidater'; +import ikon from './kandidatlisteboks-ikon.svg'; import './Kandidatlisteboks.css'; const Kandidatlisteboks = () => { - const {valgtOrganisasjon} = useContext(OrganisasjonsDetaljerContext); - const [antallKandidater, setantallKandidater] = useState(0); - useEffect(() => { - if (valgtOrganisasjon) - hentAntallKandidater(valgtOrganisasjon.organisasjon.OrganizationNumber).then(antallKandidater => - setantallKandidater(antallKandidater) - ); - }, [valgtOrganisasjon]); + const { data } = useAntallKandidater(); + + const antallKandidater = data?.antallKandidater ?? 0; + const orgnummerFraUrl = new URLSearchParams(window.location.search).get('bedrift') ?? ''; - const href = kandidatlisteURL + (orgnummerFraUrl === '' ? '' : `?virksomhet=${orgnummerFraUrl}`); + const href = + kandidatlisteURL + (orgnummerFraUrl === '' ? '' : `?virksomhet=${orgnummerFraUrl}`); - return antallKandidater === 0 - ? null - : -
- {antallKandidater}kandidater -
+
+ + {' '} + + {antallKandidater} + kandidater{' '} + +
+ ); }; export default Kandidatlisteboks; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx index 879275590..8d7ecd9ac 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx @@ -1,21 +1,22 @@ -import React, {useContext, useEffect, useState} from 'react'; -import {tiltaksgjennomforingURL} from '../../../../lenker'; -import {OrganisasjonsDetaljerContext} from '../../../OrganisasjonDetaljerProvider'; +import React, { useContext, useEffect, useState } from 'react'; +import { tiltaksgjennomforingURL } from '../../../../lenker'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; import './Tiltakboks.css'; import tiltakikon from './tiltakboks-ikon.svg'; -import {Arbeidsavtale, hentArbeidsavtaler} from '../../../../api/arbeidsavtalerApi'; -import {Tjenesteboks} from "../Tjenesteboks"; -import {BodyShort} from "@navikt/ds-react"; -import * as Record from '../../../../utils/Record' +import { Arbeidsavtale, useArbeidsavtaler } from '../../../../api/useArbeidsavtaler'; +import { Tjenesteboks } from '../Tjenesteboks'; +import { BodyShort } from '@navikt/ds-react'; +import * as Record from '../../../../utils/Record'; +import * as Sentry from '@sentry/browser'; const displayname = { - 'ARBEIDSTRENING': 'arbeidstrening', - 'MIDLERTIDIG_LONNSTILSKUDD': 'lønnstilskudd', - 'VARIG_LONNSTILSKUDD': 'varig lønnstilskudd', - 'SOMMERJOBB': 'sommerjobb', - 'INKLUDERINGSTILSKUDD': 'inkluderingstilskudd', - 'MENTOR': 'mentortilskudd', -} + ARBEIDSTRENING: 'arbeidstrening', + MIDLERTIDIG_LONNSTILSKUDD: 'lønnstilskudd', + VARIG_LONNSTILSKUDD: 'varig lønnstilskudd', + SOMMERJOBB: 'sommerjobb', + INKLUDERINGSTILSKUDD: 'inkluderingstilskudd', + MENTOR: 'mentortilskudd', +}; const displayorder: (keyof typeof displayname)[] = [ 'ARBEIDSTRENING', @@ -24,75 +25,64 @@ const displayorder: (keyof typeof displayname)[] = [ 'SOMMERJOBB', 'INKLUDERINGSTILSKUDD', 'MENTOR', -] +]; const Tiltakboks = () => { const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - const orgnr = valgtOrganisasjon?.organisasjon?.OrganizationNumber - const [avtaleoversikt, setAvtaleoversikt] = useState>({}) + const orgnr = valgtOrganisasjon?.organisasjon?.OrganizationNumber; + + const { data: avtaler = [] } = useArbeidsavtaler(); - useEffect(() => { - if (orgnr !== undefined) { - hentArbeidsavtaler(orgnr) - .then((avtaler: Arbeidsavtale[]) => { - const avtalerMedTiltaktype = (tiltaktype: string) => - avtaler.filter((avtale: Arbeidsavtale) => avtale.tiltakstype === tiltaktype).length; - setAvtaleoversikt({ - 'ARBEIDSTRENING': avtalerMedTiltaktype('ARBEIDSTRENING'), - 'MIDLERTIDIG_LONNSTILSKUDD': avtalerMedTiltaktype('MIDLERTIDIG_LONNSTILSKUDD'), - 'VARIG_LONNSTILSKUDD': avtalerMedTiltaktype('VARIG_LONNSTILSKUDD'), - 'SOMMERJOBB': avtalerMedTiltaktype('SOMMERJOBB'), - 'INKLUDERINGSTILSKUDD': avtalerMedTiltaktype('INKLUDERINGSTILSKUDD'), - 'MENTOR': avtalerMedTiltaktype('MENTOR'), - }) - }) - .catch(_ => { - setAvtaleoversikt({}) - }); - } - }, [orgnr]); + const avtalerMedTiltaktype = (tiltaktype: string) => + avtaler.filter((avtale: Arbeidsavtale) => avtale.tiltakstype === tiltaktype).length; - const tiltakUrl = orgnr !== undefined && orgnr !== '' - ? `${tiltaksgjennomforingURL}&bedrift=${orgnr}` - : tiltaksgjennomforingURL; + const tiltakUrl = + orgnr !== undefined && orgnr !== '' + ? `${tiltaksgjennomforingURL}&bedrift=${orgnr}` + : tiltaksgjennomforingURL; - const tallElems = displayorder.flatMap(avtaletype => { - const antall = avtaleoversikt[avtaletype] ?? 0; + const tallElems = displayorder.flatMap((avtaletype) => { + const antall = avtalerMedTiltaktype(avtaletype); return antall > 0 ? [ -
{antall}
, -
{displayname[avtaletype]}
, - ] +
+ {antall} +
, +
+ {displayname[avtaletype]} +
, + ] : []; - }) + }); - return -
- {tallElems.length > 0 - ?
- { tallElems } -
- : + return ( + - ; + > +
+ {tallElems.length > 0 ? ( +
{tallElems}
+ ) : ( + + )} +
+ + ); }; -const TekstUtenTall = () => +const TekstUtenTall = () => ( <> - + Arbeidstrening, lønnstilskudd, mentortilskudd, inkluderingstilskudd og sommerjobb. - - De ulike tiltakene krever egne tilganger i Altinn - - ; - + De ulike tiltakene krever egne tilganger i Altinn + +); export default Tiltakboks; diff --git a/src/api/arbeidsavtalerApi.ts b/src/api/arbeidsavtalerApi.ts deleted file mode 100644 index 1a4fee3a4..000000000 --- a/src/api/arbeidsavtalerApi.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface Arbeidsavtale { - tiltakstype: string; -} - -export async function hentArbeidsavtaler( - orgnr: string, -): Promise> { - const respons = await fetch(`/min-side-arbeidsgiver/tiltaksgjennomforing-api/avtaler/min-side-arbeidsgiver?bedriftNr=${orgnr}`); - if (respons.ok) { - return respons.json(); - } - return []; -} \ No newline at end of file diff --git a/src/api/arbeidsforholdApi.ts b/src/api/arbeidsforholdApi.ts deleted file mode 100644 index dc8d162aa..000000000 --- a/src/api/arbeidsforholdApi.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { z } from 'zod'; -import * as Sentry from '@sentry/browser'; -import { Severity } from '@sentry/react'; - -const Oversikt = z.object({ - second: z.number().optional(), -}); - -export async function hentAntallArbeidsforholdFraAareg( - underenhet: string, - enhet: string -): Promise { - const respons = await fetch('/min-side-arbeidsgiver/antall-arbeidsforhold', { - headers: { - jurenhet: enhet, - orgnr: underenhet, - }, - }); - - if (!respons.ok) { - Sentry.captureMessage( - `hent antall arbeidsforhold fra aareg feilet med ${respons.status}`, - Severity.Warning - ); - return -1; - } - - try { - const { second = 0 } = Oversikt.parse(await respons.json()); - return second === 0 ? -1 : second; - } catch (error) { - Sentry.captureException(error); - return -1; - } -} diff --git a/src/api/presenterteKandidaterApi.ts b/src/api/presenterteKandidaterApi.ts deleted file mode 100644 index 2bc614205..000000000 --- a/src/api/presenterteKandidaterApi.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from 'zod'; -import * as Sentry from "@sentry/browser"; -import {Severity} from "@sentry/react"; - -const PresenterteKandidater = z.object({ - antallKandidater: z.number(), -}) - -export async function hentAntallKandidater( - orgnr: string, -): Promise { - const respons = await fetch( - `/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern/antallkandidater?virksomhetsnummer=${orgnr}`, - ); - - if (!respons.ok) { - Sentry.captureMessage(`hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}`, Severity.Warning) - return 0 - } - - try { - const {antallKandidater = 0} = PresenterteKandidater.parse(await respons.json()) - return antallKandidater - } catch (error) { - Sentry.captureException(error) - return 0 - } -} - diff --git "a/src/api/sykefrav\303\246rStatistikkApi.ts" "b/src/api/sykefrav\303\246rStatistikkApi.ts" deleted file mode 100644 index b16c78e2c..000000000 --- "a/src/api/sykefrav\303\246rStatistikkApi.ts" +++ /dev/null @@ -1,27 +0,0 @@ -import {z} from "zod"; -import * as Sentry from "@sentry/browser"; - -const Sykefraværsrespons = z.object({ - type: z.string(), - label: z.string(), - prosent: z.number(), -}); -export type Sykefraværsrespons = z.infer | undefined; - -export async function hentSykefravær( - orgnr: string, -): Promise { - const url = `/min-side-arbeidsgiver/api/sykefravaerstatistikk/${orgnr}`; - const respons = await fetch(url); - if (respons.ok) { - try { - return respons.status === 204 ? undefined : Sykefraværsrespons.parse(await respons.json()); - } catch (error) { - Sentry.captureException(error) - return undefined - } - } - throw new Error(`Kall til '${url}' feilet med ${respons.status}:${respons.statusText}`); -} - - diff --git a/src/api/useAntallArbeidsforholdFraAareg.ts b/src/api/useAntallArbeidsforholdFraAareg.ts new file mode 100644 index 000000000..42deb9045 --- /dev/null +++ b/src/api/useAntallArbeidsforholdFraAareg.ts @@ -0,0 +1,42 @@ +import { z } from 'zod'; +import useSWR, { SWRResponse } from 'swr'; +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; +import * as Sentry from '@sentry/browser'; + +const Oversikt = z.object({ + second: z.number().optional(), +}); + +export type AntallArbeidsforholdType = z.infer; + +export const useAntallArbeidsforholdFraAareg = (): SWRResponse => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + + const arbeidsgiverURL = '/min-side-arbeidsgiver/antall-arbeidsforhold'; + const headers = { + headers: + valgtOrganisasjon !== undefined + ? { + jurenhet: valgtOrganisasjon.organisasjon.ParentOrganizationNumber ?? '', + orgnr: valgtOrganisasjon.organisasjon.OrganizationNumber, + } + : {}, + }; + + const fetcher = async (url: string, headers: {}) => { + const respons = await fetch(url, headers); + return Oversikt.parse(await respons.json()); + }; + + const respons = useSWR( + valgtOrganisasjon !== undefined ? [arbeidsgiverURL, headers] : null, + ([url, headers]) => fetcher(url, headers) + ); + + const { error } = respons; + if (error !== undefined) { + Sentry.captureException(error); + } + return respons; +}; diff --git a/src/api/useAntallKandidater.ts b/src/api/useAntallKandidater.ts new file mode 100644 index 000000000..092a7d160 --- /dev/null +++ b/src/api/useAntallKandidater.ts @@ -0,0 +1,32 @@ +import { z } from 'zod'; +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; +import useSWR, { SWRResponse } from 'swr'; +import * as Sentry from '@sentry/browser'; + +const PresenterteKandidater = z.object({ + antallKandidater: z.number(), +}); + +type PresenterteKandidater = z.infer; + +export const useAntallKandidater = (): SWRResponse => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + + const orgnr = valgtOrganisasjon?.organisasjon.OrganizationNumber; + const url = `/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern/antallkandidater?virksomhetsnummer=${orgnr}`; + + const fetcher = async (url: string) => { + const respons = await fetch(url); + return PresenterteKandidater.parse(await respons.json()); + }; + + const respons = useSWR(valgtOrganisasjon !== undefined ? url : null, fetcher); + const { error } = respons; + + if (error !== undefined) { + Sentry.captureException(error); + } + + return respons; +}; diff --git a/src/api/useArbeidsavtaler.ts b/src/api/useArbeidsavtaler.ts new file mode 100644 index 000000000..9296a6347 --- /dev/null +++ b/src/api/useArbeidsavtaler.ts @@ -0,0 +1,35 @@ +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; +import useSWR, { SWRResponse } from 'swr'; +import { z } from 'zod'; +import * as Sentry from '@sentry/browser'; + +export interface Arbeidsavtale { + tiltakstype: string; +} + +const arbeidsavtaleResponsType = z.array( + z.object({ + tiltakstype: z.string(), + }) +); + +export const useArbeidsavtaler = (): SWRResponse, any> => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + const orgnr = valgtOrganisasjon?.organisasjon?.OrganizationNumber; + + const url = `/min-side-arbeidsgiver/tiltaksgjennomforing-api/avtaler/min-side-arbeidsgiver?bedriftNr=${orgnr}`; + const fetcher = async (url: string) => { + const respons = await fetch(url); + return arbeidsavtaleResponsType.parse(await respons.json()); + }; + + const respons = useSWR(valgtOrganisasjon !== undefined ? url : null, fetcher); + const { error } = respons; + + if (error !== undefined) { + Sentry.captureException(error); + } + + return respons; +}; diff --git "a/src/api/useSykefrav\303\246r.ts" "b/src/api/useSykefrav\303\246r.ts" new file mode 100644 index 000000000..8d836f552 --- /dev/null +++ "b/src/api/useSykefrav\303\246r.ts" @@ -0,0 +1,33 @@ +import { z } from 'zod'; +import useSWR, { SWRResponse } from 'swr'; +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; +import * as Sentry from '@sentry/browser'; + +const Sykefraværsrespons = z.object({ + type: z.string(), + label: z.string(), + prosent: z.number(), +}); +export type Sykefraværsrespons = z.infer; + +export const useSykefravær = (): SWRResponse => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + const orgnr = valgtOrganisasjon?.organisasjon.OrganizationNumber; + + const url = `/min-side-arbeidsgiver/api/sykefravaerstatistikk/${orgnr}`; + + const fetcher = async (url: string) => { + const respons = await fetch(url); + return Sykefraværsrespons.parse(await respons.json()); + }; + + const respons = useSWR(valgtOrganisasjon !== undefined ? url : null, fetcher); + const { error } = respons; + + if (error !== undefined) { + Sentry.captureException(error); + } + + return respons; +}; From 252faeafa369fa7aa419fba9b24ddffbc9c6496f Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Wed, 30 Aug 2023 07:39:38 +0200 Subject: [PATCH 02/25] =?UTF-8?q?bruk=20userinfo=20api=20til=20=C3=A5=20he?= =?UTF-8?q?nte=20organinsasjoner=20og=20tilganger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/mock/altinnMeldingsboksMock.js | 25 +- server/mock/altinnMock.js | 218 ------------- server/mock/userInfoMock.js | 308 ++++++++++++++++++ server/server.js | 2 +- src/App/OrganisasjonerOgTilgangerProvider.tsx | 222 ++++++++----- src/altinn/organisasjon.ts | 8 +- src/altinn/tilganger.ts | 53 +-- src/api/dnaApi.ts | 166 ++++++---- src/index.tsx | 2 +- 9 files changed, 580 insertions(+), 424 deletions(-) delete mode 100644 server/mock/altinnMock.js create mode 100644 server/mock/userInfoMock.js diff --git a/server/mock/altinnMeldingsboksMock.js b/server/mock/altinnMeldingsboksMock.js index 0106afe2c..9b1fa4b91 100644 --- a/server/mock/altinnMeldingsboksMock.js +++ b/server/mock/altinnMeldingsboksMock.js @@ -1,9 +1,9 @@ -import {OrganisasjonerResponse} from './altinnMock.js'; +import { OrganisasjonerResponse } from './userInfoMock.js'; const reportees = { _links: {}, _embedded: { - reportees: OrganisasjonerResponse.map(org => ({ + reportees: OrganisasjonerResponse.map((org) => ({ ...org, _links: { self: { @@ -25,7 +25,7 @@ let getRandomInt = (min, max) => { let randomStatus = () => (Math.random() < 0.5 ? 'Ulest' : 'Lest'); -const uniqueMessageId = (id => () => { +const uniqueMessageId = ((id) => () => { id += 1; return `r${id}`; })(0); @@ -59,8 +59,7 @@ const randomMessage = (org) => { href: '/min-side-arbeidsgiver/mock/tt02.altinn.no/api/r50179335/messages/a9069768', }, portalview: { - href: - '/min-side-arbeidsgiver/mock/tt02.altinn.no/Pages/ServiceEngine/Correspondence/Correspondences.aspx?ReporteeElementID=9069768&ESC=5562&ESEC=1', + href: '/min-side-arbeidsgiver/mock/tt02.altinn.no/Pages/ServiceEngine/Correspondence/Correspondences.aspx?ReporteeElementID=9069768&ESC=5562&ESEC=1', }, metadata: { href: '/min-side-arbeidsgiver/mock/tt02.altinn.no/api/metadata/correspondence/5562/1', @@ -79,7 +78,7 @@ const randomMessages = (org) => { }; let allMessages = {}; -OrganisasjonerResponse.forEach(org => { +OrganisasjonerResponse.forEach((org) => { allMessages[`reportee${org.OrganizationNumber}`] = { _links: { portalview: { @@ -100,10 +99,10 @@ const getMessagesForReportee = (reporteeId) => { }; export const mock = (app) => { - app.use('/min-side-arbeidsgiver/mock/tt02.altinn.no/api/reportees', (req, res) => { - res.send(reportees); - }); - app.use(`/min-side-arbeidsgiver/mock/tt02.altinn.no/api/:id/messages`, (req, res) => { - res.send(getMessagesForReportee(req.params.id)); - }); - } \ No newline at end of file + app.use('/min-side-arbeidsgiver/mock/tt02.altinn.no/api/reportees', (req, res) => { + res.send(reportees); + }); + app.use(`/min-side-arbeidsgiver/mock/tt02.altinn.no/api/:id/messages`, (req, res) => { + res.send(getMessagesForReportee(req.params.id)); + }); +}; diff --git a/server/mock/altinnMock.js b/server/mock/altinnMock.js deleted file mode 100644 index 1f21ec06b..000000000 --- a/server/mock/altinnMock.js +++ /dev/null @@ -1,218 +0,0 @@ -import casual from 'casual'; - -export const OrganisasjonerResponse = [ - { - Name: 'En Juridisk Ehhet AS', - Type: 'Enterprise', - ParentOrganizationNumber: null, - OrganizationNumber: '812345674', - OrganizationForm: 'AS', - Status: 'Active', - }, - { - Name: 'BALLSTAD OG HAMARØY', - Type: 'Business', - OrganizationNumber: '182345674', - ParentOrganizationNumber: '118345674', - OrganizationForm: 'AAFY', - Status: 'Active', - }, - { - Name: 'BALLSTAD OG HORTEN', - Type: 'Enterprise', - ParentOrganizationNumber: null, - OrganizationNumber: '118345674', - OrganizationForm: 'FLI', - Status: 'Active', - }, - { - Name: 'TEST AV AAFY ', - Type: 'Business', - OrganizationNumber: '119845674', - ParentOrganizationNumber: '118985674', - OrganizationForm: 'AAFY', - Status: 'Active', - }, - { - Name: 'NAV ENGERDAL', - Type: 'Business', - ParentOrganizationNumber: '812345674', - OrganizationNumber: '119985432', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'NAV HAMAR', - Type: 'Business', - ParentOrganizationNumber: '812345674', - OrganizationNumber: '119988432', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'BJØRNØYA OG ROVDE REVISJON', - Type: 'Enterprise', - ParentOrganizationNumber: null, - OrganizationNumber: '123988321', - OrganizationForm: 'AS', - Status: 'Active', - }, - { - Name: 'ARENDAL OG BØNES REVISJON', - Type: 'Business', - ParentOrganizationNumber: '123988321', - OrganizationNumber: '321988123', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'GRAVDAL OG SOLLIA REVISJON', - Type: 'Business', - ParentOrganizationNumber: '123988321', - OrganizationNumber: '311288223', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'STORFOSNA OG FREDRIKSTAD REGNSKAP', - Type: 'Business', - ParentOrganizationNumber: '311388333', - OrganizationNumber: '411488444', - OrganizationForm: 'AAFY', - Status: 'Active', - }, - { - Name: 'TRANØY OG SANDE I VESTFOLD REGNSKAP', - Type: 'Enterprise', - ParentOrganizationNumber: null, - OrganizationNumber: '311388333', - OrganizationForm: 'FLI', - Status: 'Active', - }, - { - Name: 'BIRTAVARRE OG VÆRLANDET FORELDER', - Type: 'Enterprise', - OrganizationNumber: '121488424', - OrganizationForm: 'AS', - Status: 'Active', - }, - { - Name: 'SALTRØD OG HØNEBY', - Type: 'Business', - OrganizationNumber: '999999999', - ParentOrganizationNumber: '121488424', - OrganizationForm: 'BEDR', - Status: 'Active', - } -]; -casual.define('orgnr', () => casual.integer(100000000, 999999999).toString()); - -casual.define('underenhet', (parentOrganizationNumber) => ({ - Name: casual.company_name, - Type: 'Business', - OrganizationNumber: casual.orgnr, - ParentOrganizationNumber: parentOrganizationNumber, - OrganizationForm: 'BEDR', - Status: 'Active', -})); - -casual.define('hovedenhet', (organizationNumber) => ({ - Name: casual.company_name, - Type: 'Enterprise', - ParentOrganizationNumber: null, - OrganizationNumber: organizationNumber, - OrganizationForm: 'AS', - Status: 'Active', -})); - -const generateUnderenheter = () => { - const orgnummer = casual.orgnr; - const underenheter = Array(casual.integer(1, 11)).fill(null).map(() => casual.underenhet(orgnummer)); - const hovedenhet = casual.hovedenhet(orgnummer); - return [hovedenhet, ...underenheter]; -} - -const andreOrganisasjoner = Array(40).fill(null).flatMap(() => { - return generateUnderenheter(); -}); - - -const organisasjonerMedRettigheter = [ - '182345674', - '118345674', - '822565212', - '922658986', - '121488424', - '999999999', -]; -const rettigheterSkjemaDefaultResponse = OrganisasjonerResponse - .filter(({OrganizationNumber}) => organisasjonerMedRettigheter.includes(OrganizationNumber)); - -const mentortilskuddskjemaResponse = [ - { - Name: 'BALLSTAD OG HAMARØY', - Type: 'Business', - OrganizationNumber: '182345674', - ParentOrganizationNumber: '118345674', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'BALLSTAD OG HORTEN', - Type: 'Enterprise', - OrganizationNumber: '118345674', - OrganizationForm: 'AS', - Status: 'Active', - }, - { - Name: 'NAV ENGERDAL', - Type: 'Business', - ParentOrganizationNumber: '812345674', - OrganizationNumber: '119985432', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'NAV HAMAR', - Type: 'Business', - ParentOrganizationNumber: '812345674', - OrganizationNumber: '119988432', - OrganizationForm: 'BEDR', - Status: 'Active', - } -]; - - -const InntektsmeldingSkjemaResponse = [ - { - Name: 'BALLSTAD OG HAMARØY', - Type: 'Business', - OrganizationNumber: '182345674', - ParentOrganizationNumber: '118345674', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'BALLSTAD OG HORTEN', - Type: 'Enterprise', - OrganizationNumber: '118345674', - OrganizationForm: 'AS', - Status: 'Active', - }, -]; - -export const mock = (app) => { - app.use('/min-side-arbeidsgiver/api/organisasjoner', (req, res) => res.send([...OrganisasjonerResponse, ...andreOrganisasjoner])); - app.use( - '/min-side-arbeidsgiver/api/rettigheter-til-skjema/?serviceKode=5216&serviceEdition=1', - (req, res) => res.send(mentortilskuddskjemaResponse) - ); - app.use( - '/min-side-arbeidsgiver/api/rettigheter-til-skjema/?serviceKode=4936&serviceEdition=1', - (req, res) => res.send(InntektsmeldingSkjemaResponse) - ); - app.use( - '/min-side-arbeidsgiver/api/rettigheter-til-skjema/', - (req, res) => res.send(rettigheterSkjemaDefaultResponse) - ); -} \ No newline at end of file diff --git a/server/mock/userInfoMock.js b/server/mock/userInfoMock.js new file mode 100644 index 000000000..971d536ff --- /dev/null +++ b/server/mock/userInfoMock.js @@ -0,0 +1,308 @@ +import casual from 'casual'; + +export const OrganisasjonerResponse = [ + { + Name: 'En Juridisk Ehhet AS', + Type: 'Enterprise', + ParentOrganizationNumber: null, + OrganizationNumber: '812345674', + OrganizationForm: 'AS', + Status: 'Active', + }, + { + Name: 'BALLSTAD OG HAMARØY', + Type: 'Business', + OrganizationNumber: '182345674', + ParentOrganizationNumber: '118345674', + OrganizationForm: 'AAFY', + Status: 'Active', + }, + { + Name: 'BALLSTAD OG HORTEN', + Type: 'Enterprise', + ParentOrganizationNumber: null, + OrganizationNumber: '118345674', + OrganizationForm: 'FLI', + Status: 'Active', + }, + { + Name: 'TEST AV AAFY ', + Type: 'Business', + OrganizationNumber: '119845674', + ParentOrganizationNumber: '118985674', + OrganizationForm: 'AAFY', + Status: 'Active', + }, + { + Name: 'NAV ENGERDAL', + Type: 'Business', + ParentOrganizationNumber: '812345674', + OrganizationNumber: '119985432', + OrganizationForm: 'BEDR', + Status: 'Active', + }, + { + Name: 'NAV HAMAR', + Type: 'Business', + ParentOrganizationNumber: '812345674', + OrganizationNumber: '119988432', + OrganizationForm: 'BEDR', + Status: 'Active', + }, + { + Name: 'BJØRNØYA OG ROVDE REVISJON', + Type: 'Enterprise', + ParentOrganizationNumber: null, + OrganizationNumber: '123988321', + OrganizationForm: 'AS', + Status: 'Active', + }, + { + Name: 'ARENDAL OG BØNES REVISJON', + Type: 'Business', + ParentOrganizationNumber: '123988321', + OrganizationNumber: '321988123', + OrganizationForm: 'BEDR', + Status: 'Active', + }, + { + Name: 'GRAVDAL OG SOLLIA REVISJON', + Type: 'Business', + ParentOrganizationNumber: '123988321', + OrganizationNumber: '311288223', + OrganizationForm: 'BEDR', + Status: 'Active', + }, + { + Name: 'STORFOSNA OG FREDRIKSTAD REGNSKAP', + Type: 'Business', + ParentOrganizationNumber: '311388333', + OrganizationNumber: '411488444', + OrganizationForm: 'AAFY', + Status: 'Active', + }, + { + Name: 'TRANØY OG SANDE I VESTFOLD REGNSKAP', + Type: 'Enterprise', + ParentOrganizationNumber: null, + OrganizationNumber: '311388333', + OrganizationForm: 'FLI', + Status: 'Active', + }, + { + Name: 'BIRTAVARRE OG VÆRLANDET FORELDER', + Type: 'Enterprise', + OrganizationNumber: '121488424', + OrganizationForm: 'AS', + Status: 'Active', + }, + { + Name: 'SALTRØD OG HØNEBY', + Type: 'Business', + OrganizationNumber: '999999999', + ParentOrganizationNumber: '121488424', + OrganizationForm: 'BEDR', + Status: 'Active', + }, +]; +casual.define('orgnr', () => casual.integer(100000000, 999999999).toString()); + +casual.define('underenhet', (parentOrganizationNumber) => ({ + Name: casual.company_name, + Type: 'Business', + OrganizationNumber: casual.orgnr, + ParentOrganizationNumber: parentOrganizationNumber, + OrganizationForm: 'BEDR', + Status: 'Active', +})); + +casual.define('hovedenhet', (organizationNumber) => ({ + Name: casual.company_name, + Type: 'Enterprise', + ParentOrganizationNumber: null, + OrganizationNumber: organizationNumber, + OrganizationForm: 'AS', + Status: 'Active', +})); + +const generateUnderenheter = () => { + const orgnummer = casual.orgnr; + const underenheter = Array(casual.integer(1, 11)) + .fill(null) + .map(() => casual.underenhet(orgnummer)); + const hovedenhet = casual.hovedenhet(orgnummer); + return [hovedenhet, ...underenheter]; +}; + +const andreOrganisasjoner = Array(40) + .fill(null) + .flatMap(() => { + return generateUnderenheter(); + }); + +const organisasjonerMedRettigheter = [ + '182345674', + '118345674', + '822565212', + '922658986', + '121488424', + '999999999', +]; + +const rettigheterSkjemaDefaultResponse = OrganisasjonerResponse.filter(({ OrganizationNumber }) => + organisasjonerMedRettigheter.includes(OrganizationNumber) +); +const alleTjenester = [ + { + id: 'ekspertbistand', + tjenestekode: '5384', + tjenesteversjon: '1', + }, + { + id: 'inntektsmelding', + tjenestekode: '4936', + tjenesteversjon: '1', + }, + { + id: 'utsendtArbeidstakerEØS', + tjenestekode: '4826', + tjenesteversjon: '1', + }, + { + id: 'arbeidstrening', + tjenestekode: '5332', + tjenesteversjon: '1', + }, + { + id: 'arbeidsforhold', + tjenestekode: '5441', + tjenesteversjon: '1', + }, + { + id: 'midlertidigLønnstilskudd', + tjenestekode: '5516', + tjenesteversjon: '1', + }, + { + id: 'varigLønnstilskudd', + tjenestekode: '5516', + tjenesteversjon: '2', + }, + { + id: 'sommerjobb', + tjenestekode: '5516', + tjenesteversjon: '3', + }, + { + id: 'mentortilskudd', + tjenestekode: '5516', + tjenesteversjon: '4', + }, + { + id: 'inkluderingstilskudd', + tjenestekode: '5516', + tjenesteversjon: '5', + }, + { + id: 'sykefravarstatistikk', + tjenestekode: '3403', + tjenesteversjon: '1', + }, + { + id: 'forebyggefravar', + tjenestekode: '5934', + tjenesteversjon: '1', + }, + { + id: 'rekruttering', + tjenestekode: '5078', + tjenesteversjon: '1', + }, + { + id: 'tilskuddsbrev', + tjenestekode: '5278', + tjenesteversjon: '1', + }, + { + id: 'yrkesskade', + tjenestekode: '5902', + tjenesteversjon: '1', + }, +]; + +export const mock = (app) => { + app.use('/min-side-arbeidsgiver/api/userInfo/v1', (req, res) => { + return res.send({ + altinnError: casual.boolean, + organisasjoner: [...OrganisasjonerResponse, ...andreOrganisasjoner], + tilganger: [ + { + id: 'mentortilskudd', + tjenestekode: '5216', + tjenesteversjon: '1', + organisasjoner: [ + { + Name: 'BALLSTAD OG HAMARØY', + Type: 'Business', + OrganizationNumber: '182345674', + ParentOrganizationNumber: '118345674', + OrganizationForm: 'BEDR', + Status: 'Active', + }, + { + Name: 'BALLSTAD OG HORTEN', + Type: 'Enterprise', + OrganizationNumber: '118345674', + OrganizationForm: 'AS', + Status: 'Active', + }, + { + Name: 'NAV ENGERDAL', + Type: 'Business', + ParentOrganizationNumber: '812345674', + OrganizationNumber: '119985432', + OrganizationForm: 'BEDR', + Status: 'Active', + }, + { + Name: 'NAV HAMAR', + Type: 'Business', + ParentOrganizationNumber: '812345674', + OrganizationNumber: '119988432', + OrganizationForm: 'BEDR', + Status: 'Active', + }, + ], + }, + { + id: 'inntektsmelding', + tjenestekode: '4936', + tjenesteversjon: '1', + organisasjoner: [ + { + Name: 'BALLSTAD OG HAMARØY', + Type: 'Business', + OrganizationNumber: '182345674', + ParentOrganizationNumber: '118345674', + OrganizationForm: 'BEDR', + Status: 'Active', + }, + { + Name: 'BALLSTAD OG HORTEN', + Type: 'Enterprise', + OrganizationNumber: '118345674', + OrganizationForm: 'AS', + Status: 'Active', + }, + ], + }, + ...alleTjenester + .filter(({ id }) => id !== 'mentortilskudd' && id !== 'inntektsmelding') + .map((tjeneste) => ({ + ...tjeneste, + organisasjoner: rettigheterSkjemaDefaultResponse, + })), + ], + }); + }); +}; diff --git a/server/server.js b/server/server.js index 961f59547..74cedf252 100644 --- a/server/server.js +++ b/server/server.js @@ -92,7 +92,7 @@ const main = async () => { (await import('./mock/innloggetMock.js')).mock(app); (await import('./mock/pamMock.js')).mock(app); (await import('./mock/syfoMock.js')).mock(app); - (await import('./mock/altinnMock.js')).mock(app); + (await import('./mock/userInfoMock.js')).mock(app); (await import('./mock/altinnMeldingsboksMock.js')).mock(app); (await import('./mock/altinnBeOmTilgangMock.js')).mock(app); (await import('./mock/enhetsRegisteretMock.js')).mock(app); diff --git a/src/App/OrganisasjonerOgTilgangerProvider.tsx b/src/App/OrganisasjonerOgTilgangerProvider.tsx index a1b6266a3..622875b18 100644 --- a/src/App/OrganisasjonerOgTilgangerProvider.tsx +++ b/src/App/OrganisasjonerOgTilgangerProvider.tsx @@ -1,28 +1,32 @@ -import React, {FunctionComponent, useContext, useEffect, useMemo, useState} from 'react'; +import React, { FunctionComponent, useContext, useEffect, useMemo, useState } from 'react'; import { DigiSyfoOrganisasjon, - hentOrganisasjoner, hentRefusjonstatus, hentSyfoVirksomheter, - RefusjonStatus + hentUserInfo, + RefusjonStatus, } from '../api/dnaApi'; -import {autentiserAltinnBruker, hentAltinnRaporteeIdentiteter, ReporteeMessagesUrls} from '../api/altinnApi'; +import { + autentiserAltinnBruker, + hentAltinnRaporteeIdentiteter, + ReporteeMessagesUrls, +} from '../api/altinnApi'; import * as Record from '../utils/Record'; -import {AltinnTilgangssøknad, hentAltinntilganger, hentAltinnTilgangssøknader} from '../altinn/tilganger'; -import {altinntjeneste, AltinntjenesteId} from '../altinn/tjenester'; -import {SpinnerMedBanner} from './Spinner'; +import { AltinnTilgangssøknad, hentAltinnTilgangssøknader } from '../altinn/tilganger'; +import { altinntjeneste, AltinntjenesteId } from '../altinn/tjenester'; +import { SpinnerMedBanner } from './Spinner'; import amplitude from '../utils/amplitude'; -import {Organisasjon} from '../altinn/organisasjon'; -import {AlertContext} from './Alerts/Alerts'; -import * as Sentry from "@sentry/browser"; +import { Organisasjon } from '../altinn/organisasjon'; +import { AlertContext } from './Alerts/Alerts'; +import * as Sentry from '@sentry/browser'; import { byggOrganisasjonstre } from './ByggOrganisasjonstre'; import { useEffectfulAsyncFunction } from './hooks/useValueFromEffect'; -import { Set, Map } from 'immutable' +import { Map, Set } from 'immutable'; type orgnr = string; export type Søknadsstatus = - { tilgang: 'søknad opprettet'; url: string } + | { tilgang: 'søknad opprettet'; url: string } | { tilgang: 'søkt' } | { tilgang: 'godkjent' } | { tilgang: 'ikke søkt' }; @@ -36,8 +40,8 @@ export type OrganisasjonInfo = { reporteetilgang: boolean; refusjonstatustilgang: boolean; refusjonstatus: { - "KLAR_FOR_INNSENDING"?: number, - }, + KLAR_FOR_INNSENDING?: number; + }; }; export enum SyfoTilgang { @@ -47,9 +51,9 @@ export enum SyfoTilgang { } export type OrganisasjonEnhet = { - hovedenhet: Organisasjon, - underenheter: Organisasjon[] -} + hovedenhet: Organisasjon; + underenheter: Organisasjon[]; +}; export type Context = { organisasjoner: Record; @@ -70,57 +74,98 @@ const beregnOrganisasjoner = ( altinntilganger: Record> | undefined, altinnTilgangssøknader: AltinnTilgangssøknad[] | undefined, tilgangTilSyfo: SyfoTilgang, - alleRefusjonsstatus: RefusjonStatus[] | undefined, + alleRefusjonsstatus: RefusjonStatus[] | undefined ): Record | undefined => { - if (!(altinnorganisasjoner && syfoVirksomheter && altinntilganger && altinnTilgangssøknader && tilgangTilSyfo !== SyfoTilgang.LASTER && alleRefusjonsstatus !== undefined)) { + if ( + !( + altinnorganisasjoner && + syfoVirksomheter && + altinntilganger && + altinnTilgangssøknader && + tilgangTilSyfo !== SyfoTilgang.LASTER && + alleRefusjonsstatus !== undefined + ) + ) { return undefined; } - const virksomheter = [...altinnorganisasjoner, ...syfoVirksomheter.map(({ organisasjon }) => organisasjon)] + const virksomheter = [ + ...altinnorganisasjoner, + ...syfoVirksomheter.map(({ organisasjon }) => organisasjon), + ]; return Record.fromEntries( virksomheter.map((org) => { - const refusjonstatus = alleRefusjonsstatus.find(({ virksomhetsnummer }) => virksomhetsnummer === org.OrganizationNumber); + const refusjonstatus = alleRefusjonsstatus.find( + ({ virksomhetsnummer }) => virksomhetsnummer === org.OrganizationNumber + ); return [ org.OrganizationNumber, { organisasjon: org, - altinntilgang: - Record.map(altinntilganger, (id: AltinntjenesteId, orgnrMedTilgang: Set): boolean => - orgnrMedTilgang.has(org.OrganizationNumber), - ), - altinnsøknad: Record.map(altinntilganger, + altinntilgang: Record.map( + altinntilganger, + (id: AltinntjenesteId, orgnrMedTilgang: Set): boolean => + orgnrMedTilgang.has(org.OrganizationNumber) + ), + altinnsøknad: Record.map( + altinntilganger, (id: AltinntjenesteId, _orgnrMedTilgang: Set) => - sjekkTilgangssøknader(org.OrganizationNumber, id, _orgnrMedTilgang, altinnTilgangssøknader), + sjekkTilgangssøknader( + org.OrganizationNumber, + id, + _orgnrMedTilgang, + altinnTilgangssøknader + ) + ), + syfotilgang: syfoVirksomheter.some( + ({ organisasjon }) => + organisasjon.OrganizationNumber === org.OrganizationNumber + ), + antallSykmeldte: + syfoVirksomheter.find( + ({ organisasjon }) => + organisasjon.OrganizationNumber === org.OrganizationNumber + )?.antallSykmeldte ?? 0, + reporteetilgang: altinnorganisasjoner.some( + ({ OrganizationNumber }) => OrganizationNumber === org.OrganizationNumber ), - syfotilgang: syfoVirksomheter.some(({ organisasjon }) => organisasjon.OrganizationNumber === org.OrganizationNumber), - antallSykmeldte: syfoVirksomheter.find(({ organisasjon }) => organisasjon.OrganizationNumber === org.OrganizationNumber)?.antallSykmeldte ?? 0, - reporteetilgang: altinnorganisasjoner.some(({ OrganizationNumber }) => OrganizationNumber === org.OrganizationNumber), refusjonstatus: refusjonstatus?.statusoversikt ?? {}, refusjonstatustilgang: refusjonstatus?.tilgang ?? false, }, ]; - })); -} + }) + ); +}; const measureAll = (done: (duration: number) => void, ...args: Promise[]) => { - const started = performance.now() + const started = performance.now(); Promise.all(args).finally(() => { - done(performance.now() - started) - }) -} + done(performance.now() - started); + }); +}; -export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { - const [altinnorganisasjoner, setAltinnorganisasjoner] = useState(undefined); - const [altinntilganger, setAltinntilganger] = useState> | undefined>(undefined); - const [altinnTilgangssøknader, setAltinnTilgangssøknader] = useState([]); +export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { + const [altinnorganisasjoner, setAltinnorganisasjoner] = useState( + undefined + ); + const [altinntilganger, setAltinntilganger] = useState< + Record> | undefined + >(undefined); + const [altinnTilgangssøknader, setAltinnTilgangssøknader] = useState< + AltinnTilgangssøknad[] | undefined + >([]); const [reporteeMessagesUrls, setReporteeMessagesUrls] = useState({}); - const [syfoVirksomheter, setSyfoVirksomheter] = useState(undefined); + const [syfoVirksomheter, setSyfoVirksomheter] = useState( + undefined + ); const [tilgangTilSyfo, setTilgangTilSyfo] = useState(SyfoTilgang.LASTER); const [visSyfoFeilmelding, setVisSyfoFeilmelding] = useState(false); const [visFeilmelding, setVisFeilmelding] = useState(false); - const [alleRefusjonsstatus, setAlleRefusjonsstatus] = useState(undefined); - const {addAlert} = useContext(AlertContext) + const [alleRefusjonsstatus, setAlleRefusjonsstatus] = useState( + undefined + ); + const { addAlert } = useContext(AlertContext); useEffect(() => { measureAll( (tidMs) => { @@ -129,18 +174,33 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { tidMs, }); }, - hentOrganisasjoner() - .then(orgs => { - const gyldigeOrganisasjoner = orgs.filter( - org => + hentUserInfo() + .then(({ organisasjoner, tilganger, altinnError }) => { + if (altinnError) { + setVisFeilmelding(true); + addAlert('TilgangerAltinn'); + } + + // TODO: flytt filter til backend + const gyldigeOrganisasjoner = organisasjoner.filter( + (org) => org.OrganizationForm === 'BEDR' || org.OrganizationForm === 'AAFY' || - org.Type === 'Enterprise', + org.Type === 'Enterprise' ); setAltinnorganisasjoner(gyldigeOrganisasjoner); + setAltinntilganger( + Record.fromEntries( + tilganger.map((it) => [ + it.id, + // TODO: vurder minimering av kontrakt fra backend. trenger kun orgnr i tilgangen + Set(it.organisasjoner.map((it) => it.OrganizationNumber)), + ]) + ) + ); if (gyldigeOrganisasjoner.length !== 0) { - hentAltinnRaporteeIdentiteter().then(result => { + hentAltinnRaporteeIdentiteter().then((result) => { if (result instanceof Error) { autentiserAltinnBruker(window.location.href); setReporteeMessagesUrls({}); @@ -154,16 +214,11 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { }) .catch((error) => { Sentry.captureException(error); + setAltinntilganger(Record.map(altinntjeneste, () => Set())); setAltinnorganisasjoner([]); setVisFeilmelding(true); addAlert('TilgangerAltinn'); }), - hentAltinntilganger() - .then(setAltinntilganger) - .catch((error) => { - Sentry.captureException(error); - setAltinntilganger(Record.map(altinntjeneste, () => Set())); - }), hentAltinnTilgangssøknader() .then(setAltinnTilgangssøknader) .catch((error) => { @@ -171,9 +226,11 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { setAltinnTilgangssøknader([]); }), hentSyfoVirksomheter() - .then(virksomheter => { + .then((virksomheter) => { setSyfoVirksomheter(virksomheter); - setTilgangTilSyfo(virksomheter.length > 0 ? SyfoTilgang.TILGANG : SyfoTilgang.IKKE_TILGANG); + setTilgangTilSyfo( + virksomheter.length > 0 ? SyfoTilgang.TILGANG : SyfoTilgang.IKKE_TILGANG + ); amplitude.setUserProperties({ syfotilgang: virksomheter.length > 0 }); }) .catch((error) => { @@ -184,7 +241,7 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { addAlert('TilgangerDigiSyfo'); }), hentRefusjonstatus() - .then(refusjonstatus => { + .then((refusjonstatus) => { setAlleRefusjonsstatus(refusjonstatus); }) .catch((error) => { @@ -193,45 +250,50 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { // har ikke egen alert type på dette, da det mest sannsynlig er altinn som feiler setVisFeilmelding(true); addAlert('TilgangerAltinn'); - }), + }) ); }, []); - const beregnOrganisasjonerArgs = [ altinnorganisasjoner, syfoVirksomheter, altinntilganger, altinnTilgangssøknader, tilgangTilSyfo, - alleRefusjonsstatus - ] as const + alleRefusjonsstatus, + ] as const; const organisasjoner = useMemo( () => beregnOrganisasjoner(...beregnOrganisasjonerArgs), - beregnOrganisasjonerArgs, - ) + beregnOrganisasjonerArgs + ); const [organisasjonstreResponse, error] = useEffectfulAsyncFunction( undefined as OrganisasjonEnhet[] | undefined, byggOrganisasjonstre, [organisasjoner] - ) + ); const organisasjonstre = error ? [] : organisasjonstreResponse; const childrenMap = useMemo( - () => Map( - (organisasjonstre ?? []).map(({hovedenhet, underenheter}): [string, Set] => - [hovedenhet.OrganizationNumber, Set(underenheter.map(it => it.OrganizationNumber))] - ) - ), + () => + Map( + (organisasjonstre ?? []).map( + ({ hovedenhet, underenheter }): [string, Set] => [ + hovedenhet.OrganizationNumber, + Set(underenheter.map((it) => it.OrganizationNumber)), + ] + ) + ), [organisasjonstre] - ) + ); if (organisasjoner !== undefined && organisasjonstre !== undefined) { const detFinnesEnUnderenhetMedParent = () => { - return Record.values(organisasjoner).some(org => org.organisasjon.ParentOrganizationNumber); + return Record.values(organisasjoner).some( + (org) => org.organisasjon.ParentOrganizationNumber + ); }; const harTilganger = detFinnesEnUnderenhetMedParent() && Record.length(organisasjoner) > 0; @@ -252,9 +314,7 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { ); } else { - return ( - - ); + return ; } }; @@ -262,26 +322,26 @@ const sjekkTilgangssøknader = ( orgnr: orgnr, id: AltinntjenesteId, _orgnrMedTilgang: Set, - altinnTilgangssøknader: AltinnTilgangssøknad[], + altinnTilgangssøknader: AltinnTilgangssøknad[] ): Søknadsstatus => { const { tjenestekode, tjenesteversjon } = altinntjeneste[id]; const søknader = altinnTilgangssøknader.filter( - s => + (s) => s.orgnr === orgnr && s.serviceCode === tjenestekode && - s.serviceEdition.toString() === tjenesteversjon, + s.serviceEdition.toString() === tjenesteversjon ); - if (søknader.some(_ => _.status === 'Unopened')) { + if (søknader.some((_) => _.status === 'Unopened')) { return { tilgang: 'søkt' }; } - const søknad = søknader.find(_ => _.status === 'Created'); + const søknad = søknader.find((_) => _.status === 'Created'); if (søknad) { return { tilgang: 'søknad opprettet', url: søknad.submitUrl }; } - if (søknader.some(_ => _.status === 'Accepted')) { + if (søknader.some((_) => _.status === 'Accepted')) { return { tilgang: 'godkjent' }; } return { tilgang: 'ikke søkt' }; diff --git a/src/altinn/organisasjon.ts b/src/altinn/organisasjon.ts index 8f2ce0c60..6d80b8a8e 100644 --- a/src/altinn/organisasjon.ts +++ b/src/altinn/organisasjon.ts @@ -1,4 +1,4 @@ -import {z} from "zod"; +import { z } from 'zod'; export const Organisasjon = z.object({ Name: z.string(), @@ -6,7 +6,11 @@ export const Organisasjon = z.object({ OrganizationNumber: z.string(), OrganizationForm: z.string(), Status: z.string(), - ParentOrganizationNumber: z.string().nullable().transform(o => o ?? ""), + ParentOrganizationNumber: z + .string() + .nullable() + .default('') + .transform((o) => o ?? ''), }); export type Organisasjon = z.infer; diff --git a/src/altinn/tilganger.ts b/src/altinn/tilganger.ts index f1f1ab0c9..287c2a343 100644 --- a/src/altinn/tilganger.ts +++ b/src/altinn/tilganger.ts @@ -1,37 +1,4 @@ -import { altinntjeneste, AltinnFellesInfo, AltinntjenesteId } from './tjenester'; -import * as Record from '../utils/Record'; -import { Organisasjon } from './organisasjon'; -import { Set } from 'immutable' - -type Orgnr = string; - -export const hentAltinntilganger = async (): Promise>> => { - const enkelttilganger = await Promise.all( - Record.mapToArray(altinntjeneste, hentAltinntilgangerForEnTjeneste) - ); - return Record.fromEntries(enkelttilganger); -}; - -const hentAltinntilgangerForEnTjeneste = async ( - id: AltinntjenesteId, - tjeneste: AltinnFellesInfo -): Promise<[AltinntjenesteId, Set]> => { - const respons = await fetch( - '/min-side-arbeidsgiver/api/rettigheter-til-skjema/?serviceKode=' + - tjeneste.tjenestekode + - '&serviceEdition=' + - tjeneste.tjenesteversjon - ); - - let organisasjoner: Organisasjon[] = []; - - if (respons.ok) { - organisasjoner = await respons.json(); - } - - const orgnr = organisasjoner.map(_ => _.OrganizationNumber); - return [id, Set(orgnr)]; -}; +import { altinntjeneste, AltinntjenesteId } from './tjenester'; const altinnTilgangssøknadUrl = '/min-side-arbeidsgiver/api/altinn-tilgangssoknad'; @@ -67,26 +34,28 @@ export interface AltinnTilgangssøknadskjemaDTO { serviceEdition: number; } -export const opprettAltinnTilgangssøknad = async (skjema: AltinnTilgangssøknadskjema): Promise => { +export const opprettAltinnTilgangssøknad = async ( + skjema: AltinnTilgangssøknadskjema +): Promise => { const dto: AltinnTilgangssøknadskjemaDTO = { orgnr: skjema.orgnr, redirectUrl: skjema.redirectUrl, serviceCode: altinntjeneste[skjema.altinnId].tjenestekode, - serviceEdition: parseInt(altinntjeneste[skjema.altinnId].tjenesteversjon) - } + serviceEdition: parseInt(altinntjeneste[skjema.altinnId].tjenesteversjon), + }; const response = await fetch(altinnTilgangssøknadUrl, { - 'method' : 'POST', + method: 'POST', body: JSON.stringify(dto), headers: { - 'content-type': 'application/json', - 'accept': 'application/json' - } + 'content-type': 'application/json', + accept: 'application/json', + }, }); if (!response.ok) { return null; } - return await response.json() as AltinnTilgangssøknad; + return (await response.json()) as AltinnTilgangssøknad; }; diff --git a/src/api/dnaApi.ts b/src/api/dnaApi.ts index 3b2ca96a6..fc93cd4ba 100644 --- a/src/api/dnaApi.ts +++ b/src/api/dnaApi.ts @@ -1,7 +1,7 @@ -import {Organisasjon} from '../altinn/organisasjon'; -import {z} from "zod"; -import * as Sentry from "@sentry/browser"; - +import { Organisasjon } from '../altinn/organisasjon'; +import { z } from 'zod'; +import * as Sentry from '@sentry/browser'; +import { AltinntjenesteId } from '../altinn/tjenester'; const digiSyfoVirksomheterURL = '/min-side-arbeidsgiver/api/narmesteleder/virksomheter-v3'; const DigiSyfoOrganisasjon = z.object({ @@ -17,16 +17,17 @@ export async function hentSyfoVirksomheter(): Promise; @@ -39,25 +40,41 @@ export async function hentRefusjonstatus(): Promise { try { return RefusjonStatusResponse.parse(data); } catch (error) { - Sentry.captureException(error) + Sentry.captureException(error); } } - throw new Error(`Kall til ${refusjonstatusURL} feilet med ${respons.status}:${respons.statusText}`); + throw new Error( + `Kall til ${refusjonstatusURL} feilet med ${respons.status}:${respons.statusText}` + ); } - const sjekkInnloggetURL = '/min-side-arbeidsgiver/api/innlogget'; export const sjekkInnlogget = async (): Promise => { - const {ok} = await fetch(sjekkInnloggetURL) - return ok -} + const { ok } = await fetch(sjekkInnloggetURL); + return ok; +}; -export async function hentOrganisasjoner(): Promise { - const respons = await fetch('/min-side-arbeidsgiver/api/organisasjoner'); +const UserInfoRespons = z.object({ + altinnError: z.boolean(), + organisasjoner: z.array(Organisasjon), + tilganger: z.array( + z.object({ + id: z.custom(), + tjenestekode: z.string(), + tjenesteversjon: z.string(), + organisasjoner: z.array(Organisasjon), + }) + ), +}); +export type UserInfo = z.infer; +export async function hentUserInfo(): Promise { + const respons = await fetch('/min-side-arbeidsgiver/api/userInfo/v1'); if (respons.ok) { - return await respons.json(); + return UserInfoRespons.parse(await respons.json()); } else { - throw new Error(`Kall til '/min-side-arbeidsgiver/api/organisasjoner' feilet med ${respons.status}:${respons.statusText}`); + throw new Error( + `Kall til '/min-side-arbeidsgiver/api/userInfo/v1' feilet med ${respons.status}:${respons.statusText}` + ); } } const storageUrl = `/min-side-arbeidsgiver/api/storage`; @@ -67,8 +84,8 @@ export async function getStorage(key: string): Promise { method: 'GET', headers: { 'Content-Type': 'application/json', - 'Accept': 'application/json', - } + Accept: 'application/json', + }, }); if (respons.status === 204) { return { @@ -76,7 +93,7 @@ export async function getStorage(key: string): Promise { key, data: [], version: respons.headers.get('version'), - } + }, }; } const jsonResult = await respons.json(); @@ -85,23 +102,30 @@ export async function getStorage(key: string): Promise { key, data: jsonResult, version: respons.headers.get('version'), - } + }, }; } catch (error) { - return {error}; + return { error }; } } -export async function putStorage(key: string, data: any, version: string | null = null): Promise { +export async function putStorage( + key: string, + data: any, + version: string | null = null +): Promise { try { - const respons = await fetch(`${storageUrl}/${key}${(version !== null ? `?version=${version}` : '')}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(data), - }); + const respons = await fetch( + `${storageUrl}/${key}${version !== null ? `?version=${version}` : ''}`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify(data), + } + ); if (respons.status === 409) { return { @@ -114,38 +138,44 @@ export async function putStorage(key: string, data: any, version: string | null key, data, version, - } - } + }, + }; } else if (respons.ok) { return { updatedStorageItem: { key, data: await respons.json(), version: respons.headers.get('version'), - } + }, }; } else { return { error: { status: respons.status, statusText: respons.statusText, - } + }, }; } } catch (error) { - return {error}; + return { error }; } } -export async function deleteStorage(key: string, version: string | null = null): Promise { +export async function deleteStorage( + key: string, + version: string | null = null +): Promise { try { - const respons = await fetch(`${storageUrl}/${key}${(version !== null ? `?version=${version}` : '')}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - }); + const respons = await fetch( + `${storageUrl}/${key}${version !== null ? `?version=${version}` : ''}`, + { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + } + ); if (respons.status === 409) { return { currentStorageItem: { @@ -154,47 +184,51 @@ export async function deleteStorage(key: string, version: string | null = null): version: respons.headers.get('version'), }, rejectedStorageItem: null, - } + }; } else if (respons.ok) { return { deletedStorageItem: { key, data: await respons.json(), version: respons.headers.get('version'), - } + }, }; } else { return { error: { status: respons.status, statusText: respons.statusText, - } + }, }; } } catch (error) { - return {error}; + return { error }; } - } export type StorageItem = { - key: string, - data: any[], - version: string | null, -} + key: string; + data: any[]; + version: string | null; +}; export type StorageItemDeleted = { - deletedStorageItem: StorageItem, -} + deletedStorageItem: StorageItem; +}; export type StorageItemUpdated = { - updatedStorageItem: StorageItem, -} + updatedStorageItem: StorageItem; +}; export type StorageItemLoaded = { - loadedStorageItem: StorageItem, -} + loadedStorageItem: StorageItem; +}; export type StorageItemConflict = { - currentStorageItem: StorageItem, - rejectedStorageItem: StorageItem | null, -} + currentStorageItem: StorageItem; + rejectedStorageItem: StorageItem | null; +}; export type StorageError = { - error: any, -} -export type StorageItemResponse = StorageItemLoaded | StorageItemUpdated | StorageItemDeleted | StorageItemConflict | StorageError; \ No newline at end of file + error: any; +}; +export type StorageItemResponse = + | StorageItemLoaded + | StorageItemUpdated + | StorageItemDeleted + | StorageItemConflict + | StorageError; diff --git a/src/index.tsx b/src/index.tsx index 41babad43..ad61013a5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,7 +23,7 @@ class SentryDebugTransport implements SentryTypes.Transport { } sendEvent(event: SentryTypes.Event): PromiseLike { - console.error('would have sent to sentry', event); + console.error('would have sent to sentry', JSON.stringify(event, null, 2)); return Promise.resolve({ status: 'success' }); } } From 4f821a6815e05f11adac818b5baf422f15ab146d Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Wed, 30 Aug 2023 07:54:40 +0200 Subject: [PATCH 03/25] forenkle tilganger og flytt filtrering til backend --- server/mock/userInfoMock.js | 59 ++----------------- src/App/OrganisasjonerOgTilgangerProvider.tsx | 20 +------ src/api/dnaApi.ts | 2 +- 3 files changed, 9 insertions(+), 72 deletions(-) diff --git a/server/mock/userInfoMock.js b/server/mock/userInfoMock.js index 971d536ff..958a7a2dd 100644 --- a/server/mock/userInfoMock.js +++ b/server/mock/userInfoMock.js @@ -149,9 +149,6 @@ const organisasjonerMedRettigheter = [ '999999999', ]; -const rettigheterSkjemaDefaultResponse = OrganisasjonerResponse.filter(({ OrganizationNumber }) => - organisasjonerMedRettigheter.includes(OrganizationNumber) -); const alleTjenester = [ { id: 'ekspertbistand', @@ -240,67 +237,21 @@ export const mock = (app) => { id: 'mentortilskudd', tjenestekode: '5216', tjenesteversjon: '1', - organisasjoner: [ - { - Name: 'BALLSTAD OG HAMARØY', - Type: 'Business', - OrganizationNumber: '182345674', - ParentOrganizationNumber: '118345674', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'BALLSTAD OG HORTEN', - Type: 'Enterprise', - OrganizationNumber: '118345674', - OrganizationForm: 'AS', - Status: 'Active', - }, - { - Name: 'NAV ENGERDAL', - Type: 'Business', - ParentOrganizationNumber: '812345674', - OrganizationNumber: '119985432', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'NAV HAMAR', - Type: 'Business', - ParentOrganizationNumber: '812345674', - OrganizationNumber: '119988432', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - ], + organisasjoner: ['182345674', '118345674', '119985432', '119988432'], }, { id: 'inntektsmelding', tjenestekode: '4936', tjenesteversjon: '1', - organisasjoner: [ - { - Name: 'BALLSTAD OG HAMARØY', - Type: 'Business', - OrganizationNumber: '182345674', - ParentOrganizationNumber: '118345674', - OrganizationForm: 'BEDR', - Status: 'Active', - }, - { - Name: 'BALLSTAD OG HORTEN', - Type: 'Enterprise', - OrganizationNumber: '118345674', - OrganizationForm: 'AS', - Status: 'Active', - }, - ], + organisasjoner: ['182345674', '118345674'], }, ...alleTjenester .filter(({ id }) => id !== 'mentortilskudd' && id !== 'inntektsmelding') .map((tjeneste) => ({ ...tjeneste, - organisasjoner: rettigheterSkjemaDefaultResponse, + organisasjoner: OrganisasjonerResponse.map( + ({ OrganizationNumber }) => OrganizationNumber + ).filter((orgnr) => organisasjonerMedRettigheter.includes(orgnr)), })), ], }); diff --git a/src/App/OrganisasjonerOgTilgangerProvider.tsx b/src/App/OrganisasjonerOgTilgangerProvider.tsx index 622875b18..3c41d4d6d 100644 --- a/src/App/OrganisasjonerOgTilgangerProvider.tsx +++ b/src/App/OrganisasjonerOgTilgangerProvider.tsx @@ -180,26 +180,12 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { setVisFeilmelding(true); addAlert('TilgangerAltinn'); } - - // TODO: flytt filter til backend - const gyldigeOrganisasjoner = organisasjoner.filter( - (org) => - org.OrganizationForm === 'BEDR' || - org.OrganizationForm === 'AAFY' || - org.Type === 'Enterprise' - ); - setAltinnorganisasjoner(gyldigeOrganisasjoner); + setAltinnorganisasjoner(organisasjoner); setAltinntilganger( - Record.fromEntries( - tilganger.map((it) => [ - it.id, - // TODO: vurder minimering av kontrakt fra backend. trenger kun orgnr i tilgangen - Set(it.organisasjoner.map((it) => it.OrganizationNumber)), - ]) - ) + Record.fromEntries(tilganger.map((it) => [it.id, Set(it.organisasjoner)])) ); - if (gyldigeOrganisasjoner.length !== 0) { + if (organisasjoner.length !== 0) { hentAltinnRaporteeIdentiteter().then((result) => { if (result instanceof Error) { autentiserAltinnBruker(window.location.href); diff --git a/src/api/dnaApi.ts b/src/api/dnaApi.ts index fc93cd4ba..43911c191 100644 --- a/src/api/dnaApi.ts +++ b/src/api/dnaApi.ts @@ -62,7 +62,7 @@ const UserInfoRespons = z.object({ id: z.custom(), tjenestekode: z.string(), tjenesteversjon: z.string(), - organisasjoner: z.array(Organisasjon), + organisasjoner: z.array(z.string()), }) ), }); From 0c0b9cf9ec69428345addf2349db9f42f77fbd5f Mon Sep 17 00:00:00 2001 From: ebelegu Date: Wed, 30 Aug 2023 09:50:24 +0200 Subject: [PATCH 04/25] =?UTF-8?q?Feilh=C3=A5ndtering=20for=20antallKandida?= =?UTF-8?q?ter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/useAntallArbeidsforholdFraAareg.ts | 17 ++++++++++++++- src/api/useAntallKandidater.ts | 24 ++++++++++++++-------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/api/useAntallArbeidsforholdFraAareg.ts b/src/api/useAntallArbeidsforholdFraAareg.ts index 42deb9045..a9d369ab3 100644 --- a/src/api/useAntallArbeidsforholdFraAareg.ts +++ b/src/api/useAntallArbeidsforholdFraAareg.ts @@ -3,6 +3,7 @@ import useSWR, { SWRResponse } from 'swr'; import { useContext } from 'react'; import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; import * as Sentry from '@sentry/browser'; +import { Severity } from '@sentry/react'; const Oversikt = z.object({ second: z.number().optional(), @@ -25,8 +26,22 @@ export const useAntallArbeidsforholdFraAareg = (): SWRResponse { + const standardRespons: AntallArbeidsforholdType = { second: -1 }; const respons = await fetch(url, headers); - return Oversikt.parse(await respons.json()); + if (!respons.ok) { + Sentry.captureMessage( + `hent antall arbeidsforhold fra aareg feilet med ${respons.status}`, + Severity.Warning + ); + return standardRespons; + } + try { + const oversikt = Oversikt.parse(await respons.json()); + return oversikt.second === 0 ? standardRespons : oversikt; + } catch (error) { + Sentry.captureException(error); + return standardRespons; + } }; const respons = useSWR( diff --git a/src/api/useAntallKandidater.ts b/src/api/useAntallKandidater.ts index 092a7d160..94de40231 100644 --- a/src/api/useAntallKandidater.ts +++ b/src/api/useAntallKandidater.ts @@ -3,6 +3,7 @@ import { useContext } from 'react'; import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; import useSWR, { SWRResponse } from 'swr'; import * as Sentry from '@sentry/browser'; +import { Severity } from '@sentry/react'; const PresenterteKandidater = z.object({ antallKandidater: z.number(), @@ -18,15 +19,20 @@ export const useAntallKandidater = (): SWRResponse = const fetcher = async (url: string) => { const respons = await fetch(url); - return PresenterteKandidater.parse(await respons.json()); + if (!respons.ok) { + Sentry.captureMessage( + `hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}`, + Severity.Warning + ); + return { antallKandidater: 0 }; + } + try { + return PresenterteKandidater.parse(await respons.json()); + } catch (error) { + Sentry.captureException(error); + return { antallKandidater: 0 }; + } }; - const respons = useSWR(valgtOrganisasjon !== undefined ? url : null, fetcher); - const { error } = respons; - - if (error !== undefined) { - Sentry.captureException(error); - } - - return respons; + return useSWR(valgtOrganisasjon !== undefined ? url : null, fetcher); }; From 0b7a31b51d4151cb414c9cad0d3a02b4ad867201 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Thu, 31 Aug 2023 10:01:34 +0200 Subject: [PATCH 05/25] Revert "hent organisasjoner og tilganger fra nytt userinfo endepunkt" --- server/mock/altinnMeldingsboksMock.js | 25 +- .../mock/{userInfoMock.js => altinnMock.js} | 167 +++++--------- server/server.js | 2 +- src/App/OrganisasjonerOgTilgangerProvider.tsx | 216 +++++++----------- src/altinn/organisasjon.ts | 8 +- src/altinn/tilganger.ts | 53 ++++- src/api/dnaApi.ts | 166 ++++++-------- src/index.tsx | 2 +- 8 files changed, 273 insertions(+), 366 deletions(-) rename server/mock/{userInfoMock.js => altinnMock.js} (56%) diff --git a/server/mock/altinnMeldingsboksMock.js b/server/mock/altinnMeldingsboksMock.js index 9b1fa4b91..0106afe2c 100644 --- a/server/mock/altinnMeldingsboksMock.js +++ b/server/mock/altinnMeldingsboksMock.js @@ -1,9 +1,9 @@ -import { OrganisasjonerResponse } from './userInfoMock.js'; +import {OrganisasjonerResponse} from './altinnMock.js'; const reportees = { _links: {}, _embedded: { - reportees: OrganisasjonerResponse.map((org) => ({ + reportees: OrganisasjonerResponse.map(org => ({ ...org, _links: { self: { @@ -25,7 +25,7 @@ let getRandomInt = (min, max) => { let randomStatus = () => (Math.random() < 0.5 ? 'Ulest' : 'Lest'); -const uniqueMessageId = ((id) => () => { +const uniqueMessageId = (id => () => { id += 1; return `r${id}`; })(0); @@ -59,7 +59,8 @@ const randomMessage = (org) => { href: '/min-side-arbeidsgiver/mock/tt02.altinn.no/api/r50179335/messages/a9069768', }, portalview: { - href: '/min-side-arbeidsgiver/mock/tt02.altinn.no/Pages/ServiceEngine/Correspondence/Correspondences.aspx?ReporteeElementID=9069768&ESC=5562&ESEC=1', + href: + '/min-side-arbeidsgiver/mock/tt02.altinn.no/Pages/ServiceEngine/Correspondence/Correspondences.aspx?ReporteeElementID=9069768&ESC=5562&ESEC=1', }, metadata: { href: '/min-side-arbeidsgiver/mock/tt02.altinn.no/api/metadata/correspondence/5562/1', @@ -78,7 +79,7 @@ const randomMessages = (org) => { }; let allMessages = {}; -OrganisasjonerResponse.forEach((org) => { +OrganisasjonerResponse.forEach(org => { allMessages[`reportee${org.OrganizationNumber}`] = { _links: { portalview: { @@ -99,10 +100,10 @@ const getMessagesForReportee = (reporteeId) => { }; export const mock = (app) => { - app.use('/min-side-arbeidsgiver/mock/tt02.altinn.no/api/reportees', (req, res) => { - res.send(reportees); - }); - app.use(`/min-side-arbeidsgiver/mock/tt02.altinn.no/api/:id/messages`, (req, res) => { - res.send(getMessagesForReportee(req.params.id)); - }); -}; + app.use('/min-side-arbeidsgiver/mock/tt02.altinn.no/api/reportees', (req, res) => { + res.send(reportees); + }); + app.use(`/min-side-arbeidsgiver/mock/tt02.altinn.no/api/:id/messages`, (req, res) => { + res.send(getMessagesForReportee(req.params.id)); + }); + } \ No newline at end of file diff --git a/server/mock/userInfoMock.js b/server/mock/altinnMock.js similarity index 56% rename from server/mock/userInfoMock.js rename to server/mock/altinnMock.js index 958a7a2dd..1f21ec06b 100644 --- a/server/mock/userInfoMock.js +++ b/server/mock/altinnMock.js @@ -103,7 +103,7 @@ export const OrganisasjonerResponse = [ ParentOrganizationNumber: '121488424', OrganizationForm: 'BEDR', Status: 'Active', - }, + } ]; casual.define('orgnr', () => casual.integer(100000000, 999999999).toString()); @@ -127,18 +127,15 @@ casual.define('hovedenhet', (organizationNumber) => ({ const generateUnderenheter = () => { const orgnummer = casual.orgnr; - const underenheter = Array(casual.integer(1, 11)) - .fill(null) - .map(() => casual.underenhet(orgnummer)); + const underenheter = Array(casual.integer(1, 11)).fill(null).map(() => casual.underenhet(orgnummer)); const hovedenhet = casual.hovedenhet(orgnummer); return [hovedenhet, ...underenheter]; -}; +} + +const andreOrganisasjoner = Array(40).fill(null).flatMap(() => { + return generateUnderenheter(); +}); -const andreOrganisasjoner = Array(40) - .fill(null) - .flatMap(() => { - return generateUnderenheter(); - }); const organisasjonerMedRettigheter = [ '182345674', @@ -148,112 +145,74 @@ const organisasjonerMedRettigheter = [ '121488424', '999999999', ]; +const rettigheterSkjemaDefaultResponse = OrganisasjonerResponse + .filter(({OrganizationNumber}) => organisasjonerMedRettigheter.includes(OrganizationNumber)); -const alleTjenester = [ - { - id: 'ekspertbistand', - tjenestekode: '5384', - tjenesteversjon: '1', - }, - { - id: 'inntektsmelding', - tjenestekode: '4936', - tjenesteversjon: '1', - }, - { - id: 'utsendtArbeidstakerEØS', - tjenestekode: '4826', - tjenesteversjon: '1', - }, +const mentortilskuddskjemaResponse = [ { - id: 'arbeidstrening', - tjenestekode: '5332', - tjenesteversjon: '1', - }, - { - id: 'arbeidsforhold', - tjenestekode: '5441', - tjenesteversjon: '1', - }, - { - id: 'midlertidigLønnstilskudd', - tjenestekode: '5516', - tjenesteversjon: '1', - }, - { - id: 'varigLønnstilskudd', - tjenestekode: '5516', - tjenesteversjon: '2', - }, - { - id: 'sommerjobb', - tjenestekode: '5516', - tjenesteversjon: '3', - }, - { - id: 'mentortilskudd', - tjenestekode: '5516', - tjenesteversjon: '4', - }, - { - id: 'inkluderingstilskudd', - tjenestekode: '5516', - tjenesteversjon: '5', + Name: 'BALLSTAD OG HAMARØY', + Type: 'Business', + OrganizationNumber: '182345674', + ParentOrganizationNumber: '118345674', + OrganizationForm: 'BEDR', + Status: 'Active', }, { - id: 'sykefravarstatistikk', - tjenestekode: '3403', - tjenesteversjon: '1', + Name: 'BALLSTAD OG HORTEN', + Type: 'Enterprise', + OrganizationNumber: '118345674', + OrganizationForm: 'AS', + Status: 'Active', }, { - id: 'forebyggefravar', - tjenestekode: '5934', - tjenesteversjon: '1', + Name: 'NAV ENGERDAL', + Type: 'Business', + ParentOrganizationNumber: '812345674', + OrganizationNumber: '119985432', + OrganizationForm: 'BEDR', + Status: 'Active', }, { - id: 'rekruttering', - tjenestekode: '5078', - tjenesteversjon: '1', - }, + Name: 'NAV HAMAR', + Type: 'Business', + ParentOrganizationNumber: '812345674', + OrganizationNumber: '119988432', + OrganizationForm: 'BEDR', + Status: 'Active', + } +]; + + +const InntektsmeldingSkjemaResponse = [ { - id: 'tilskuddsbrev', - tjenestekode: '5278', - tjenesteversjon: '1', + Name: 'BALLSTAD OG HAMARØY', + Type: 'Business', + OrganizationNumber: '182345674', + ParentOrganizationNumber: '118345674', + OrganizationForm: 'BEDR', + Status: 'Active', }, { - id: 'yrkesskade', - tjenestekode: '5902', - tjenesteversjon: '1', + Name: 'BALLSTAD OG HORTEN', + Type: 'Enterprise', + OrganizationNumber: '118345674', + OrganizationForm: 'AS', + Status: 'Active', }, ]; export const mock = (app) => { - app.use('/min-side-arbeidsgiver/api/userInfo/v1', (req, res) => { - return res.send({ - altinnError: casual.boolean, - organisasjoner: [...OrganisasjonerResponse, ...andreOrganisasjoner], - tilganger: [ - { - id: 'mentortilskudd', - tjenestekode: '5216', - tjenesteversjon: '1', - organisasjoner: ['182345674', '118345674', '119985432', '119988432'], - }, - { - id: 'inntektsmelding', - tjenestekode: '4936', - tjenesteversjon: '1', - organisasjoner: ['182345674', '118345674'], - }, - ...alleTjenester - .filter(({ id }) => id !== 'mentortilskudd' && id !== 'inntektsmelding') - .map((tjeneste) => ({ - ...tjeneste, - organisasjoner: OrganisasjonerResponse.map( - ({ OrganizationNumber }) => OrganizationNumber - ).filter((orgnr) => organisasjonerMedRettigheter.includes(orgnr)), - })), - ], - }); - }); -}; + app.use('/min-side-arbeidsgiver/api/organisasjoner', (req, res) => res.send([...OrganisasjonerResponse, ...andreOrganisasjoner])); + app.use( + '/min-side-arbeidsgiver/api/rettigheter-til-skjema/?serviceKode=5216&serviceEdition=1', + (req, res) => res.send(mentortilskuddskjemaResponse) + ); + app.use( + '/min-side-arbeidsgiver/api/rettigheter-til-skjema/?serviceKode=4936&serviceEdition=1', + (req, res) => res.send(InntektsmeldingSkjemaResponse) + ); + app.use( + '/min-side-arbeidsgiver/api/rettigheter-til-skjema/', + (req, res) => res.send(rettigheterSkjemaDefaultResponse) + ); +} \ No newline at end of file diff --git a/server/server.js b/server/server.js index 74cedf252..961f59547 100644 --- a/server/server.js +++ b/server/server.js @@ -92,7 +92,7 @@ const main = async () => { (await import('./mock/innloggetMock.js')).mock(app); (await import('./mock/pamMock.js')).mock(app); (await import('./mock/syfoMock.js')).mock(app); - (await import('./mock/userInfoMock.js')).mock(app); + (await import('./mock/altinnMock.js')).mock(app); (await import('./mock/altinnMeldingsboksMock.js')).mock(app); (await import('./mock/altinnBeOmTilgangMock.js')).mock(app); (await import('./mock/enhetsRegisteretMock.js')).mock(app); diff --git a/src/App/OrganisasjonerOgTilgangerProvider.tsx b/src/App/OrganisasjonerOgTilgangerProvider.tsx index 3c41d4d6d..a1b6266a3 100644 --- a/src/App/OrganisasjonerOgTilgangerProvider.tsx +++ b/src/App/OrganisasjonerOgTilgangerProvider.tsx @@ -1,32 +1,28 @@ -import React, { FunctionComponent, useContext, useEffect, useMemo, useState } from 'react'; +import React, {FunctionComponent, useContext, useEffect, useMemo, useState} from 'react'; import { DigiSyfoOrganisasjon, + hentOrganisasjoner, hentRefusjonstatus, hentSyfoVirksomheter, - hentUserInfo, - RefusjonStatus, + RefusjonStatus } from '../api/dnaApi'; -import { - autentiserAltinnBruker, - hentAltinnRaporteeIdentiteter, - ReporteeMessagesUrls, -} from '../api/altinnApi'; +import {autentiserAltinnBruker, hentAltinnRaporteeIdentiteter, ReporteeMessagesUrls} from '../api/altinnApi'; import * as Record from '../utils/Record'; -import { AltinnTilgangssøknad, hentAltinnTilgangssøknader } from '../altinn/tilganger'; -import { altinntjeneste, AltinntjenesteId } from '../altinn/tjenester'; -import { SpinnerMedBanner } from './Spinner'; +import {AltinnTilgangssøknad, hentAltinntilganger, hentAltinnTilgangssøknader} from '../altinn/tilganger'; +import {altinntjeneste, AltinntjenesteId} from '../altinn/tjenester'; +import {SpinnerMedBanner} from './Spinner'; import amplitude from '../utils/amplitude'; -import { Organisasjon } from '../altinn/organisasjon'; -import { AlertContext } from './Alerts/Alerts'; -import * as Sentry from '@sentry/browser'; +import {Organisasjon} from '../altinn/organisasjon'; +import {AlertContext} from './Alerts/Alerts'; +import * as Sentry from "@sentry/browser"; import { byggOrganisasjonstre } from './ByggOrganisasjonstre'; import { useEffectfulAsyncFunction } from './hooks/useValueFromEffect'; -import { Map, Set } from 'immutable'; +import { Set, Map } from 'immutable' type orgnr = string; export type Søknadsstatus = - | { tilgang: 'søknad opprettet'; url: string } + { tilgang: 'søknad opprettet'; url: string } | { tilgang: 'søkt' } | { tilgang: 'godkjent' } | { tilgang: 'ikke søkt' }; @@ -40,8 +36,8 @@ export type OrganisasjonInfo = { reporteetilgang: boolean; refusjonstatustilgang: boolean; refusjonstatus: { - KLAR_FOR_INNSENDING?: number; - }; + "KLAR_FOR_INNSENDING"?: number, + }, }; export enum SyfoTilgang { @@ -51,9 +47,9 @@ export enum SyfoTilgang { } export type OrganisasjonEnhet = { - hovedenhet: Organisasjon; - underenheter: Organisasjon[]; -}; + hovedenhet: Organisasjon, + underenheter: Organisasjon[] +} export type Context = { organisasjoner: Record; @@ -74,98 +70,57 @@ const beregnOrganisasjoner = ( altinntilganger: Record> | undefined, altinnTilgangssøknader: AltinnTilgangssøknad[] | undefined, tilgangTilSyfo: SyfoTilgang, - alleRefusjonsstatus: RefusjonStatus[] | undefined + alleRefusjonsstatus: RefusjonStatus[] | undefined, ): Record | undefined => { - if ( - !( - altinnorganisasjoner && - syfoVirksomheter && - altinntilganger && - altinnTilgangssøknader && - tilgangTilSyfo !== SyfoTilgang.LASTER && - alleRefusjonsstatus !== undefined - ) - ) { + if (!(altinnorganisasjoner && syfoVirksomheter && altinntilganger && altinnTilgangssøknader && tilgangTilSyfo !== SyfoTilgang.LASTER && alleRefusjonsstatus !== undefined)) { return undefined; } - const virksomheter = [ - ...altinnorganisasjoner, - ...syfoVirksomheter.map(({ organisasjon }) => organisasjon), - ]; + const virksomheter = [...altinnorganisasjoner, ...syfoVirksomheter.map(({ organisasjon }) => organisasjon)] return Record.fromEntries( virksomheter.map((org) => { - const refusjonstatus = alleRefusjonsstatus.find( - ({ virksomhetsnummer }) => virksomhetsnummer === org.OrganizationNumber - ); + const refusjonstatus = alleRefusjonsstatus.find(({ virksomhetsnummer }) => virksomhetsnummer === org.OrganizationNumber); return [ org.OrganizationNumber, { organisasjon: org, - altinntilgang: Record.map( - altinntilganger, - (id: AltinntjenesteId, orgnrMedTilgang: Set): boolean => - orgnrMedTilgang.has(org.OrganizationNumber) - ), - altinnsøknad: Record.map( - altinntilganger, + altinntilgang: + Record.map(altinntilganger, (id: AltinntjenesteId, orgnrMedTilgang: Set): boolean => + orgnrMedTilgang.has(org.OrganizationNumber), + ), + altinnsøknad: Record.map(altinntilganger, (id: AltinntjenesteId, _orgnrMedTilgang: Set) => - sjekkTilgangssøknader( - org.OrganizationNumber, - id, - _orgnrMedTilgang, - altinnTilgangssøknader - ) - ), - syfotilgang: syfoVirksomheter.some( - ({ organisasjon }) => - organisasjon.OrganizationNumber === org.OrganizationNumber - ), - antallSykmeldte: - syfoVirksomheter.find( - ({ organisasjon }) => - organisasjon.OrganizationNumber === org.OrganizationNumber - )?.antallSykmeldte ?? 0, - reporteetilgang: altinnorganisasjoner.some( - ({ OrganizationNumber }) => OrganizationNumber === org.OrganizationNumber + sjekkTilgangssøknader(org.OrganizationNumber, id, _orgnrMedTilgang, altinnTilgangssøknader), ), + syfotilgang: syfoVirksomheter.some(({ organisasjon }) => organisasjon.OrganizationNumber === org.OrganizationNumber), + antallSykmeldte: syfoVirksomheter.find(({ organisasjon }) => organisasjon.OrganizationNumber === org.OrganizationNumber)?.antallSykmeldte ?? 0, + reporteetilgang: altinnorganisasjoner.some(({ OrganizationNumber }) => OrganizationNumber === org.OrganizationNumber), refusjonstatus: refusjonstatus?.statusoversikt ?? {}, refusjonstatustilgang: refusjonstatus?.tilgang ?? false, }, ]; - }) - ); -}; + })); +} const measureAll = (done: (duration: number) => void, ...args: Promise[]) => { - const started = performance.now(); + const started = performance.now() Promise.all(args).finally(() => { - done(performance.now() - started); - }); -}; + done(performance.now() - started) + }) +} -export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { - const [altinnorganisasjoner, setAltinnorganisasjoner] = useState( - undefined - ); - const [altinntilganger, setAltinntilganger] = useState< - Record> | undefined - >(undefined); - const [altinnTilgangssøknader, setAltinnTilgangssøknader] = useState< - AltinnTilgangssøknad[] | undefined - >([]); +export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { + const [altinnorganisasjoner, setAltinnorganisasjoner] = useState(undefined); + const [altinntilganger, setAltinntilganger] = useState> | undefined>(undefined); + const [altinnTilgangssøknader, setAltinnTilgangssøknader] = useState([]); const [reporteeMessagesUrls, setReporteeMessagesUrls] = useState({}); - const [syfoVirksomheter, setSyfoVirksomheter] = useState( - undefined - ); + const [syfoVirksomheter, setSyfoVirksomheter] = useState(undefined); const [tilgangTilSyfo, setTilgangTilSyfo] = useState(SyfoTilgang.LASTER); const [visSyfoFeilmelding, setVisSyfoFeilmelding] = useState(false); const [visFeilmelding, setVisFeilmelding] = useState(false); - const [alleRefusjonsstatus, setAlleRefusjonsstatus] = useState( - undefined - ); - const { addAlert } = useContext(AlertContext); + const [alleRefusjonsstatus, setAlleRefusjonsstatus] = useState(undefined); + const {addAlert} = useContext(AlertContext) useEffect(() => { measureAll( (tidMs) => { @@ -174,19 +129,18 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { tidMs, }); }, - hentUserInfo() - .then(({ organisasjoner, tilganger, altinnError }) => { - if (altinnError) { - setVisFeilmelding(true); - addAlert('TilgangerAltinn'); - } - setAltinnorganisasjoner(organisasjoner); - setAltinntilganger( - Record.fromEntries(tilganger.map((it) => [it.id, Set(it.organisasjoner)])) + hentOrganisasjoner() + .then(orgs => { + const gyldigeOrganisasjoner = orgs.filter( + org => + org.OrganizationForm === 'BEDR' || + org.OrganizationForm === 'AAFY' || + org.Type === 'Enterprise', ); + setAltinnorganisasjoner(gyldigeOrganisasjoner); - if (organisasjoner.length !== 0) { - hentAltinnRaporteeIdentiteter().then((result) => { + if (gyldigeOrganisasjoner.length !== 0) { + hentAltinnRaporteeIdentiteter().then(result => { if (result instanceof Error) { autentiserAltinnBruker(window.location.href); setReporteeMessagesUrls({}); @@ -200,11 +154,16 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { }) .catch((error) => { Sentry.captureException(error); - setAltinntilganger(Record.map(altinntjeneste, () => Set())); setAltinnorganisasjoner([]); setVisFeilmelding(true); addAlert('TilgangerAltinn'); }), + hentAltinntilganger() + .then(setAltinntilganger) + .catch((error) => { + Sentry.captureException(error); + setAltinntilganger(Record.map(altinntjeneste, () => Set())); + }), hentAltinnTilgangssøknader() .then(setAltinnTilgangssøknader) .catch((error) => { @@ -212,11 +171,9 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { setAltinnTilgangssøknader([]); }), hentSyfoVirksomheter() - .then((virksomheter) => { + .then(virksomheter => { setSyfoVirksomheter(virksomheter); - setTilgangTilSyfo( - virksomheter.length > 0 ? SyfoTilgang.TILGANG : SyfoTilgang.IKKE_TILGANG - ); + setTilgangTilSyfo(virksomheter.length > 0 ? SyfoTilgang.TILGANG : SyfoTilgang.IKKE_TILGANG); amplitude.setUserProperties({ syfotilgang: virksomheter.length > 0 }); }) .catch((error) => { @@ -227,7 +184,7 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { addAlert('TilgangerDigiSyfo'); }), hentRefusjonstatus() - .then((refusjonstatus) => { + .then(refusjonstatus => { setAlleRefusjonsstatus(refusjonstatus); }) .catch((error) => { @@ -236,50 +193,45 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { // har ikke egen alert type på dette, da det mest sannsynlig er altinn som feiler setVisFeilmelding(true); addAlert('TilgangerAltinn'); - }) + }), ); }, []); + const beregnOrganisasjonerArgs = [ altinnorganisasjoner, syfoVirksomheter, altinntilganger, altinnTilgangssøknader, tilgangTilSyfo, - alleRefusjonsstatus, - ] as const; + alleRefusjonsstatus + ] as const const organisasjoner = useMemo( () => beregnOrganisasjoner(...beregnOrganisasjonerArgs), - beregnOrganisasjonerArgs - ); + beregnOrganisasjonerArgs, + ) const [organisasjonstreResponse, error] = useEffectfulAsyncFunction( undefined as OrganisasjonEnhet[] | undefined, byggOrganisasjonstre, [organisasjoner] - ); + ) const organisasjonstre = error ? [] : organisasjonstreResponse; const childrenMap = useMemo( - () => - Map( - (organisasjonstre ?? []).map( - ({ hovedenhet, underenheter }): [string, Set] => [ - hovedenhet.OrganizationNumber, - Set(underenheter.map((it) => it.OrganizationNumber)), - ] - ) - ), + () => Map( + (organisasjonstre ?? []).map(({hovedenhet, underenheter}): [string, Set] => + [hovedenhet.OrganizationNumber, Set(underenheter.map(it => it.OrganizationNumber))] + ) + ), [organisasjonstre] - ); + ) if (organisasjoner !== undefined && organisasjonstre !== undefined) { const detFinnesEnUnderenhetMedParent = () => { - return Record.values(organisasjoner).some( - (org) => org.organisasjon.ParentOrganizationNumber - ); + return Record.values(organisasjoner).some(org => org.organisasjon.ParentOrganizationNumber); }; const harTilganger = detFinnesEnUnderenhetMedParent() && Record.length(organisasjoner) > 0; @@ -300,7 +252,9 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { ); } else { - return ; + return ( + + ); } }; @@ -308,26 +262,26 @@ const sjekkTilgangssøknader = ( orgnr: orgnr, id: AltinntjenesteId, _orgnrMedTilgang: Set, - altinnTilgangssøknader: AltinnTilgangssøknad[] + altinnTilgangssøknader: AltinnTilgangssøknad[], ): Søknadsstatus => { const { tjenestekode, tjenesteversjon } = altinntjeneste[id]; const søknader = altinnTilgangssøknader.filter( - (s) => + s => s.orgnr === orgnr && s.serviceCode === tjenestekode && - s.serviceEdition.toString() === tjenesteversjon + s.serviceEdition.toString() === tjenesteversjon, ); - if (søknader.some((_) => _.status === 'Unopened')) { + if (søknader.some(_ => _.status === 'Unopened')) { return { tilgang: 'søkt' }; } - const søknad = søknader.find((_) => _.status === 'Created'); + const søknad = søknader.find(_ => _.status === 'Created'); if (søknad) { return { tilgang: 'søknad opprettet', url: søknad.submitUrl }; } - if (søknader.some((_) => _.status === 'Accepted')) { + if (søknader.some(_ => _.status === 'Accepted')) { return { tilgang: 'godkjent' }; } return { tilgang: 'ikke søkt' }; diff --git a/src/altinn/organisasjon.ts b/src/altinn/organisasjon.ts index 6d80b8a8e..8f2ce0c60 100644 --- a/src/altinn/organisasjon.ts +++ b/src/altinn/organisasjon.ts @@ -1,4 +1,4 @@ -import { z } from 'zod'; +import {z} from "zod"; export const Organisasjon = z.object({ Name: z.string(), @@ -6,11 +6,7 @@ export const Organisasjon = z.object({ OrganizationNumber: z.string(), OrganizationForm: z.string(), Status: z.string(), - ParentOrganizationNumber: z - .string() - .nullable() - .default('') - .transform((o) => o ?? ''), + ParentOrganizationNumber: z.string().nullable().transform(o => o ?? ""), }); export type Organisasjon = z.infer; diff --git a/src/altinn/tilganger.ts b/src/altinn/tilganger.ts index 287c2a343..f1f1ab0c9 100644 --- a/src/altinn/tilganger.ts +++ b/src/altinn/tilganger.ts @@ -1,4 +1,37 @@ -import { altinntjeneste, AltinntjenesteId } from './tjenester'; +import { altinntjeneste, AltinnFellesInfo, AltinntjenesteId } from './tjenester'; +import * as Record from '../utils/Record'; +import { Organisasjon } from './organisasjon'; +import { Set } from 'immutable' + +type Orgnr = string; + +export const hentAltinntilganger = async (): Promise>> => { + const enkelttilganger = await Promise.all( + Record.mapToArray(altinntjeneste, hentAltinntilgangerForEnTjeneste) + ); + return Record.fromEntries(enkelttilganger); +}; + +const hentAltinntilgangerForEnTjeneste = async ( + id: AltinntjenesteId, + tjeneste: AltinnFellesInfo +): Promise<[AltinntjenesteId, Set]> => { + const respons = await fetch( + '/min-side-arbeidsgiver/api/rettigheter-til-skjema/?serviceKode=' + + tjeneste.tjenestekode + + '&serviceEdition=' + + tjeneste.tjenesteversjon + ); + + let organisasjoner: Organisasjon[] = []; + + if (respons.ok) { + organisasjoner = await respons.json(); + } + + const orgnr = organisasjoner.map(_ => _.OrganizationNumber); + return [id, Set(orgnr)]; +}; const altinnTilgangssøknadUrl = '/min-side-arbeidsgiver/api/altinn-tilgangssoknad'; @@ -34,28 +67,26 @@ export interface AltinnTilgangssøknadskjemaDTO { serviceEdition: number; } -export const opprettAltinnTilgangssøknad = async ( - skjema: AltinnTilgangssøknadskjema -): Promise => { +export const opprettAltinnTilgangssøknad = async (skjema: AltinnTilgangssøknadskjema): Promise => { const dto: AltinnTilgangssøknadskjemaDTO = { orgnr: skjema.orgnr, redirectUrl: skjema.redirectUrl, serviceCode: altinntjeneste[skjema.altinnId].tjenestekode, - serviceEdition: parseInt(altinntjeneste[skjema.altinnId].tjenesteversjon), - }; + serviceEdition: parseInt(altinntjeneste[skjema.altinnId].tjenesteversjon) + } const response = await fetch(altinnTilgangssøknadUrl, { - method: 'POST', + 'method' : 'POST', body: JSON.stringify(dto), headers: { - 'content-type': 'application/json', - accept: 'application/json', - }, + 'content-type': 'application/json', + 'accept': 'application/json' + } }); if (!response.ok) { return null; } - return (await response.json()) as AltinnTilgangssøknad; + return await response.json() as AltinnTilgangssøknad; }; diff --git a/src/api/dnaApi.ts b/src/api/dnaApi.ts index 43911c191..3b2ca96a6 100644 --- a/src/api/dnaApi.ts +++ b/src/api/dnaApi.ts @@ -1,7 +1,7 @@ -import { Organisasjon } from '../altinn/organisasjon'; -import { z } from 'zod'; -import * as Sentry from '@sentry/browser'; -import { AltinntjenesteId } from '../altinn/tjenester'; +import {Organisasjon} from '../altinn/organisasjon'; +import {z} from "zod"; +import * as Sentry from "@sentry/browser"; + const digiSyfoVirksomheterURL = '/min-side-arbeidsgiver/api/narmesteleder/virksomheter-v3'; const DigiSyfoOrganisasjon = z.object({ @@ -17,17 +17,16 @@ export async function hentSyfoVirksomheter(): Promise; @@ -40,41 +39,25 @@ export async function hentRefusjonstatus(): Promise { try { return RefusjonStatusResponse.parse(data); } catch (error) { - Sentry.captureException(error); + Sentry.captureException(error) } } - throw new Error( - `Kall til ${refusjonstatusURL} feilet med ${respons.status}:${respons.statusText}` - ); + throw new Error(`Kall til ${refusjonstatusURL} feilet med ${respons.status}:${respons.statusText}`); } + const sjekkInnloggetURL = '/min-side-arbeidsgiver/api/innlogget'; export const sjekkInnlogget = async (): Promise => { - const { ok } = await fetch(sjekkInnloggetURL); - return ok; -}; + const {ok} = await fetch(sjekkInnloggetURL) + return ok +} -const UserInfoRespons = z.object({ - altinnError: z.boolean(), - organisasjoner: z.array(Organisasjon), - tilganger: z.array( - z.object({ - id: z.custom(), - tjenestekode: z.string(), - tjenesteversjon: z.string(), - organisasjoner: z.array(z.string()), - }) - ), -}); -export type UserInfo = z.infer; -export async function hentUserInfo(): Promise { - const respons = await fetch('/min-side-arbeidsgiver/api/userInfo/v1'); +export async function hentOrganisasjoner(): Promise { + const respons = await fetch('/min-side-arbeidsgiver/api/organisasjoner'); if (respons.ok) { - return UserInfoRespons.parse(await respons.json()); + return await respons.json(); } else { - throw new Error( - `Kall til '/min-side-arbeidsgiver/api/userInfo/v1' feilet med ${respons.status}:${respons.statusText}` - ); + throw new Error(`Kall til '/min-side-arbeidsgiver/api/organisasjoner' feilet med ${respons.status}:${respons.statusText}`); } } const storageUrl = `/min-side-arbeidsgiver/api/storage`; @@ -84,8 +67,8 @@ export async function getStorage(key: string): Promise { method: 'GET', headers: { 'Content-Type': 'application/json', - Accept: 'application/json', - }, + 'Accept': 'application/json', + } }); if (respons.status === 204) { return { @@ -93,7 +76,7 @@ export async function getStorage(key: string): Promise { key, data: [], version: respons.headers.get('version'), - }, + } }; } const jsonResult = await respons.json(); @@ -102,30 +85,23 @@ export async function getStorage(key: string): Promise { key, data: jsonResult, version: respons.headers.get('version'), - }, + } }; } catch (error) { - return { error }; + return {error}; } } -export async function putStorage( - key: string, - data: any, - version: string | null = null -): Promise { +export async function putStorage(key: string, data: any, version: string | null = null): Promise { try { - const respons = await fetch( - `${storageUrl}/${key}${version !== null ? `?version=${version}` : ''}`, - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - body: JSON.stringify(data), - } - ); + const respons = await fetch(`${storageUrl}/${key}${(version !== null ? `?version=${version}` : '')}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(data), + }); if (respons.status === 409) { return { @@ -138,44 +114,38 @@ export async function putStorage( key, data, version, - }, - }; + } + } } else if (respons.ok) { return { updatedStorageItem: { key, data: await respons.json(), version: respons.headers.get('version'), - }, + } }; } else { return { error: { status: respons.status, statusText: respons.statusText, - }, + } }; } } catch (error) { - return { error }; + return {error}; } } -export async function deleteStorage( - key: string, - version: string | null = null -): Promise { +export async function deleteStorage(key: string, version: string | null = null): Promise { try { - const respons = await fetch( - `${storageUrl}/${key}${version !== null ? `?version=${version}` : ''}`, - { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - } - ); + const respons = await fetch(`${storageUrl}/${key}${(version !== null ? `?version=${version}` : '')}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + }); if (respons.status === 409) { return { currentStorageItem: { @@ -184,51 +154,47 @@ export async function deleteStorage( version: respons.headers.get('version'), }, rejectedStorageItem: null, - }; + } } else if (respons.ok) { return { deletedStorageItem: { key, data: await respons.json(), version: respons.headers.get('version'), - }, + } }; } else { return { error: { status: respons.status, statusText: respons.statusText, - }, + } }; } } catch (error) { - return { error }; + return {error}; } + } export type StorageItem = { - key: string; - data: any[]; - version: string | null; -}; + key: string, + data: any[], + version: string | null, +} export type StorageItemDeleted = { - deletedStorageItem: StorageItem; -}; + deletedStorageItem: StorageItem, +} export type StorageItemUpdated = { - updatedStorageItem: StorageItem; -}; + updatedStorageItem: StorageItem, +} export type StorageItemLoaded = { - loadedStorageItem: StorageItem; -}; + loadedStorageItem: StorageItem, +} export type StorageItemConflict = { - currentStorageItem: StorageItem; - rejectedStorageItem: StorageItem | null; -}; + currentStorageItem: StorageItem, + rejectedStorageItem: StorageItem | null, +} export type StorageError = { - error: any; -}; -export type StorageItemResponse = - | StorageItemLoaded - | StorageItemUpdated - | StorageItemDeleted - | StorageItemConflict - | StorageError; + error: any, +} +export type StorageItemResponse = StorageItemLoaded | StorageItemUpdated | StorageItemDeleted | StorageItemConflict | StorageError; \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index ad61013a5..41babad43 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,7 +23,7 @@ class SentryDebugTransport implements SentryTypes.Transport { } sendEvent(event: SentryTypes.Event): PromiseLike { - console.error('would have sent to sentry', JSON.stringify(event, null, 2)); + console.error('would have sent to sentry', event); return Promise.resolve({ status: 'success' }); } } From c1974fe3014c870748f1e655d7930717961ce7b7 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Thu, 31 Aug 2023 10:21:33 +0200 Subject: [PATCH 06/25] Revert "Revert "hent organisasjoner og tilganger fra nytt userinfo endepunkt"" --- server/mock/altinnMeldingsboksMock.js | 25 +- .../mock/{altinnMock.js => userInfoMock.js} | 167 +++++++++----- server/server.js | 2 +- src/App/OrganisasjonerOgTilgangerProvider.tsx | 216 +++++++++++------- src/altinn/organisasjon.ts | 8 +- src/altinn/tilganger.ts | 53 +---- src/api/dnaApi.ts | 166 ++++++++------ src/index.tsx | 2 +- 8 files changed, 366 insertions(+), 273 deletions(-) rename server/mock/{altinnMock.js => userInfoMock.js} (56%) diff --git a/server/mock/altinnMeldingsboksMock.js b/server/mock/altinnMeldingsboksMock.js index 0106afe2c..9b1fa4b91 100644 --- a/server/mock/altinnMeldingsboksMock.js +++ b/server/mock/altinnMeldingsboksMock.js @@ -1,9 +1,9 @@ -import {OrganisasjonerResponse} from './altinnMock.js'; +import { OrganisasjonerResponse } from './userInfoMock.js'; const reportees = { _links: {}, _embedded: { - reportees: OrganisasjonerResponse.map(org => ({ + reportees: OrganisasjonerResponse.map((org) => ({ ...org, _links: { self: { @@ -25,7 +25,7 @@ let getRandomInt = (min, max) => { let randomStatus = () => (Math.random() < 0.5 ? 'Ulest' : 'Lest'); -const uniqueMessageId = (id => () => { +const uniqueMessageId = ((id) => () => { id += 1; return `r${id}`; })(0); @@ -59,8 +59,7 @@ const randomMessage = (org) => { href: '/min-side-arbeidsgiver/mock/tt02.altinn.no/api/r50179335/messages/a9069768', }, portalview: { - href: - '/min-side-arbeidsgiver/mock/tt02.altinn.no/Pages/ServiceEngine/Correspondence/Correspondences.aspx?ReporteeElementID=9069768&ESC=5562&ESEC=1', + href: '/min-side-arbeidsgiver/mock/tt02.altinn.no/Pages/ServiceEngine/Correspondence/Correspondences.aspx?ReporteeElementID=9069768&ESC=5562&ESEC=1', }, metadata: { href: '/min-side-arbeidsgiver/mock/tt02.altinn.no/api/metadata/correspondence/5562/1', @@ -79,7 +78,7 @@ const randomMessages = (org) => { }; let allMessages = {}; -OrganisasjonerResponse.forEach(org => { +OrganisasjonerResponse.forEach((org) => { allMessages[`reportee${org.OrganizationNumber}`] = { _links: { portalview: { @@ -100,10 +99,10 @@ const getMessagesForReportee = (reporteeId) => { }; export const mock = (app) => { - app.use('/min-side-arbeidsgiver/mock/tt02.altinn.no/api/reportees', (req, res) => { - res.send(reportees); - }); - app.use(`/min-side-arbeidsgiver/mock/tt02.altinn.no/api/:id/messages`, (req, res) => { - res.send(getMessagesForReportee(req.params.id)); - }); - } \ No newline at end of file + app.use('/min-side-arbeidsgiver/mock/tt02.altinn.no/api/reportees', (req, res) => { + res.send(reportees); + }); + app.use(`/min-side-arbeidsgiver/mock/tt02.altinn.no/api/:id/messages`, (req, res) => { + res.send(getMessagesForReportee(req.params.id)); + }); +}; diff --git a/server/mock/altinnMock.js b/server/mock/userInfoMock.js similarity index 56% rename from server/mock/altinnMock.js rename to server/mock/userInfoMock.js index 1f21ec06b..958a7a2dd 100644 --- a/server/mock/altinnMock.js +++ b/server/mock/userInfoMock.js @@ -103,7 +103,7 @@ export const OrganisasjonerResponse = [ ParentOrganizationNumber: '121488424', OrganizationForm: 'BEDR', Status: 'Active', - } + }, ]; casual.define('orgnr', () => casual.integer(100000000, 999999999).toString()); @@ -127,15 +127,18 @@ casual.define('hovedenhet', (organizationNumber) => ({ const generateUnderenheter = () => { const orgnummer = casual.orgnr; - const underenheter = Array(casual.integer(1, 11)).fill(null).map(() => casual.underenhet(orgnummer)); + const underenheter = Array(casual.integer(1, 11)) + .fill(null) + .map(() => casual.underenhet(orgnummer)); const hovedenhet = casual.hovedenhet(orgnummer); return [hovedenhet, ...underenheter]; -} - -const andreOrganisasjoner = Array(40).fill(null).flatMap(() => { - return generateUnderenheter(); -}); +}; +const andreOrganisasjoner = Array(40) + .fill(null) + .flatMap(() => { + return generateUnderenheter(); + }); const organisasjonerMedRettigheter = [ '182345674', @@ -145,74 +148,112 @@ const organisasjonerMedRettigheter = [ '121488424', '999999999', ]; -const rettigheterSkjemaDefaultResponse = OrganisasjonerResponse - .filter(({OrganizationNumber}) => organisasjonerMedRettigheter.includes(OrganizationNumber)); -const mentortilskuddskjemaResponse = [ +const alleTjenester = [ { - Name: 'BALLSTAD OG HAMARØY', - Type: 'Business', - OrganizationNumber: '182345674', - ParentOrganizationNumber: '118345674', - OrganizationForm: 'BEDR', - Status: 'Active', + id: 'ekspertbistand', + tjenestekode: '5384', + tjenesteversjon: '1', }, { - Name: 'BALLSTAD OG HORTEN', - Type: 'Enterprise', - OrganizationNumber: '118345674', - OrganizationForm: 'AS', - Status: 'Active', + id: 'inntektsmelding', + tjenestekode: '4936', + tjenesteversjon: '1', }, { - Name: 'NAV ENGERDAL', - Type: 'Business', - ParentOrganizationNumber: '812345674', - OrganizationNumber: '119985432', - OrganizationForm: 'BEDR', - Status: 'Active', + id: 'utsendtArbeidstakerEØS', + tjenestekode: '4826', + tjenesteversjon: '1', }, { - Name: 'NAV HAMAR', - Type: 'Business', - ParentOrganizationNumber: '812345674', - OrganizationNumber: '119988432', - OrganizationForm: 'BEDR', - Status: 'Active', - } -]; - - -const InntektsmeldingSkjemaResponse = [ + id: 'arbeidstrening', + tjenestekode: '5332', + tjenesteversjon: '1', + }, { - Name: 'BALLSTAD OG HAMARØY', - Type: 'Business', - OrganizationNumber: '182345674', - ParentOrganizationNumber: '118345674', - OrganizationForm: 'BEDR', - Status: 'Active', + id: 'arbeidsforhold', + tjenestekode: '5441', + tjenesteversjon: '1', }, { - Name: 'BALLSTAD OG HORTEN', - Type: 'Enterprise', - OrganizationNumber: '118345674', - OrganizationForm: 'AS', - Status: 'Active', + id: 'midlertidigLønnstilskudd', + tjenestekode: '5516', + tjenesteversjon: '1', + }, + { + id: 'varigLønnstilskudd', + tjenestekode: '5516', + tjenesteversjon: '2', + }, + { + id: 'sommerjobb', + tjenestekode: '5516', + tjenesteversjon: '3', + }, + { + id: 'mentortilskudd', + tjenestekode: '5516', + tjenesteversjon: '4', + }, + { + id: 'inkluderingstilskudd', + tjenestekode: '5516', + tjenesteversjon: '5', + }, + { + id: 'sykefravarstatistikk', + tjenestekode: '3403', + tjenesteversjon: '1', + }, + { + id: 'forebyggefravar', + tjenestekode: '5934', + tjenesteversjon: '1', + }, + { + id: 'rekruttering', + tjenestekode: '5078', + tjenesteversjon: '1', + }, + { + id: 'tilskuddsbrev', + tjenestekode: '5278', + tjenesteversjon: '1', + }, + { + id: 'yrkesskade', + tjenestekode: '5902', + tjenesteversjon: '1', }, ]; export const mock = (app) => { - app.use('/min-side-arbeidsgiver/api/organisasjoner', (req, res) => res.send([...OrganisasjonerResponse, ...andreOrganisasjoner])); - app.use( - '/min-side-arbeidsgiver/api/rettigheter-til-skjema/?serviceKode=5216&serviceEdition=1', - (req, res) => res.send(mentortilskuddskjemaResponse) - ); - app.use( - '/min-side-arbeidsgiver/api/rettigheter-til-skjema/?serviceKode=4936&serviceEdition=1', - (req, res) => res.send(InntektsmeldingSkjemaResponse) - ); - app.use( - '/min-side-arbeidsgiver/api/rettigheter-til-skjema/', - (req, res) => res.send(rettigheterSkjemaDefaultResponse) - ); -} \ No newline at end of file + app.use('/min-side-arbeidsgiver/api/userInfo/v1', (req, res) => { + return res.send({ + altinnError: casual.boolean, + organisasjoner: [...OrganisasjonerResponse, ...andreOrganisasjoner], + tilganger: [ + { + id: 'mentortilskudd', + tjenestekode: '5216', + tjenesteversjon: '1', + organisasjoner: ['182345674', '118345674', '119985432', '119988432'], + }, + { + id: 'inntektsmelding', + tjenestekode: '4936', + tjenesteversjon: '1', + organisasjoner: ['182345674', '118345674'], + }, + ...alleTjenester + .filter(({ id }) => id !== 'mentortilskudd' && id !== 'inntektsmelding') + .map((tjeneste) => ({ + ...tjeneste, + organisasjoner: OrganisasjonerResponse.map( + ({ OrganizationNumber }) => OrganizationNumber + ).filter((orgnr) => organisasjonerMedRettigheter.includes(orgnr)), + })), + ], + }); + }); +}; diff --git a/server/server.js b/server/server.js index 961f59547..74cedf252 100644 --- a/server/server.js +++ b/server/server.js @@ -92,7 +92,7 @@ const main = async () => { (await import('./mock/innloggetMock.js')).mock(app); (await import('./mock/pamMock.js')).mock(app); (await import('./mock/syfoMock.js')).mock(app); - (await import('./mock/altinnMock.js')).mock(app); + (await import('./mock/userInfoMock.js')).mock(app); (await import('./mock/altinnMeldingsboksMock.js')).mock(app); (await import('./mock/altinnBeOmTilgangMock.js')).mock(app); (await import('./mock/enhetsRegisteretMock.js')).mock(app); diff --git a/src/App/OrganisasjonerOgTilgangerProvider.tsx b/src/App/OrganisasjonerOgTilgangerProvider.tsx index a1b6266a3..3c41d4d6d 100644 --- a/src/App/OrganisasjonerOgTilgangerProvider.tsx +++ b/src/App/OrganisasjonerOgTilgangerProvider.tsx @@ -1,28 +1,32 @@ -import React, {FunctionComponent, useContext, useEffect, useMemo, useState} from 'react'; +import React, { FunctionComponent, useContext, useEffect, useMemo, useState } from 'react'; import { DigiSyfoOrganisasjon, - hentOrganisasjoner, hentRefusjonstatus, hentSyfoVirksomheter, - RefusjonStatus + hentUserInfo, + RefusjonStatus, } from '../api/dnaApi'; -import {autentiserAltinnBruker, hentAltinnRaporteeIdentiteter, ReporteeMessagesUrls} from '../api/altinnApi'; +import { + autentiserAltinnBruker, + hentAltinnRaporteeIdentiteter, + ReporteeMessagesUrls, +} from '../api/altinnApi'; import * as Record from '../utils/Record'; -import {AltinnTilgangssøknad, hentAltinntilganger, hentAltinnTilgangssøknader} from '../altinn/tilganger'; -import {altinntjeneste, AltinntjenesteId} from '../altinn/tjenester'; -import {SpinnerMedBanner} from './Spinner'; +import { AltinnTilgangssøknad, hentAltinnTilgangssøknader } from '../altinn/tilganger'; +import { altinntjeneste, AltinntjenesteId } from '../altinn/tjenester'; +import { SpinnerMedBanner } from './Spinner'; import amplitude from '../utils/amplitude'; -import {Organisasjon} from '../altinn/organisasjon'; -import {AlertContext} from './Alerts/Alerts'; -import * as Sentry from "@sentry/browser"; +import { Organisasjon } from '../altinn/organisasjon'; +import { AlertContext } from './Alerts/Alerts'; +import * as Sentry from '@sentry/browser'; import { byggOrganisasjonstre } from './ByggOrganisasjonstre'; import { useEffectfulAsyncFunction } from './hooks/useValueFromEffect'; -import { Set, Map } from 'immutable' +import { Map, Set } from 'immutable'; type orgnr = string; export type Søknadsstatus = - { tilgang: 'søknad opprettet'; url: string } + | { tilgang: 'søknad opprettet'; url: string } | { tilgang: 'søkt' } | { tilgang: 'godkjent' } | { tilgang: 'ikke søkt' }; @@ -36,8 +40,8 @@ export type OrganisasjonInfo = { reporteetilgang: boolean; refusjonstatustilgang: boolean; refusjonstatus: { - "KLAR_FOR_INNSENDING"?: number, - }, + KLAR_FOR_INNSENDING?: number; + }; }; export enum SyfoTilgang { @@ -47,9 +51,9 @@ export enum SyfoTilgang { } export type OrganisasjonEnhet = { - hovedenhet: Organisasjon, - underenheter: Organisasjon[] -} + hovedenhet: Organisasjon; + underenheter: Organisasjon[]; +}; export type Context = { organisasjoner: Record; @@ -70,57 +74,98 @@ const beregnOrganisasjoner = ( altinntilganger: Record> | undefined, altinnTilgangssøknader: AltinnTilgangssøknad[] | undefined, tilgangTilSyfo: SyfoTilgang, - alleRefusjonsstatus: RefusjonStatus[] | undefined, + alleRefusjonsstatus: RefusjonStatus[] | undefined ): Record | undefined => { - if (!(altinnorganisasjoner && syfoVirksomheter && altinntilganger && altinnTilgangssøknader && tilgangTilSyfo !== SyfoTilgang.LASTER && alleRefusjonsstatus !== undefined)) { + if ( + !( + altinnorganisasjoner && + syfoVirksomheter && + altinntilganger && + altinnTilgangssøknader && + tilgangTilSyfo !== SyfoTilgang.LASTER && + alleRefusjonsstatus !== undefined + ) + ) { return undefined; } - const virksomheter = [...altinnorganisasjoner, ...syfoVirksomheter.map(({ organisasjon }) => organisasjon)] + const virksomheter = [ + ...altinnorganisasjoner, + ...syfoVirksomheter.map(({ organisasjon }) => organisasjon), + ]; return Record.fromEntries( virksomheter.map((org) => { - const refusjonstatus = alleRefusjonsstatus.find(({ virksomhetsnummer }) => virksomhetsnummer === org.OrganizationNumber); + const refusjonstatus = alleRefusjonsstatus.find( + ({ virksomhetsnummer }) => virksomhetsnummer === org.OrganizationNumber + ); return [ org.OrganizationNumber, { organisasjon: org, - altinntilgang: - Record.map(altinntilganger, (id: AltinntjenesteId, orgnrMedTilgang: Set): boolean => - orgnrMedTilgang.has(org.OrganizationNumber), - ), - altinnsøknad: Record.map(altinntilganger, + altinntilgang: Record.map( + altinntilganger, + (id: AltinntjenesteId, orgnrMedTilgang: Set): boolean => + orgnrMedTilgang.has(org.OrganizationNumber) + ), + altinnsøknad: Record.map( + altinntilganger, (id: AltinntjenesteId, _orgnrMedTilgang: Set) => - sjekkTilgangssøknader(org.OrganizationNumber, id, _orgnrMedTilgang, altinnTilgangssøknader), + sjekkTilgangssøknader( + org.OrganizationNumber, + id, + _orgnrMedTilgang, + altinnTilgangssøknader + ) + ), + syfotilgang: syfoVirksomheter.some( + ({ organisasjon }) => + organisasjon.OrganizationNumber === org.OrganizationNumber + ), + antallSykmeldte: + syfoVirksomheter.find( + ({ organisasjon }) => + organisasjon.OrganizationNumber === org.OrganizationNumber + )?.antallSykmeldte ?? 0, + reporteetilgang: altinnorganisasjoner.some( + ({ OrganizationNumber }) => OrganizationNumber === org.OrganizationNumber ), - syfotilgang: syfoVirksomheter.some(({ organisasjon }) => organisasjon.OrganizationNumber === org.OrganizationNumber), - antallSykmeldte: syfoVirksomheter.find(({ organisasjon }) => organisasjon.OrganizationNumber === org.OrganizationNumber)?.antallSykmeldte ?? 0, - reporteetilgang: altinnorganisasjoner.some(({ OrganizationNumber }) => OrganizationNumber === org.OrganizationNumber), refusjonstatus: refusjonstatus?.statusoversikt ?? {}, refusjonstatustilgang: refusjonstatus?.tilgang ?? false, }, ]; - })); -} + }) + ); +}; const measureAll = (done: (duration: number) => void, ...args: Promise[]) => { - const started = performance.now() + const started = performance.now(); Promise.all(args).finally(() => { - done(performance.now() - started) - }) -} + done(performance.now() - started); + }); +}; -export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { - const [altinnorganisasjoner, setAltinnorganisasjoner] = useState(undefined); - const [altinntilganger, setAltinntilganger] = useState> | undefined>(undefined); - const [altinnTilgangssøknader, setAltinnTilgangssøknader] = useState([]); +export const OrganisasjonerOgTilgangerProvider: FunctionComponent = (props) => { + const [altinnorganisasjoner, setAltinnorganisasjoner] = useState( + undefined + ); + const [altinntilganger, setAltinntilganger] = useState< + Record> | undefined + >(undefined); + const [altinnTilgangssøknader, setAltinnTilgangssøknader] = useState< + AltinnTilgangssøknad[] | undefined + >([]); const [reporteeMessagesUrls, setReporteeMessagesUrls] = useState({}); - const [syfoVirksomheter, setSyfoVirksomheter] = useState(undefined); + const [syfoVirksomheter, setSyfoVirksomheter] = useState( + undefined + ); const [tilgangTilSyfo, setTilgangTilSyfo] = useState(SyfoTilgang.LASTER); const [visSyfoFeilmelding, setVisSyfoFeilmelding] = useState(false); const [visFeilmelding, setVisFeilmelding] = useState(false); - const [alleRefusjonsstatus, setAlleRefusjonsstatus] = useState(undefined); - const {addAlert} = useContext(AlertContext) + const [alleRefusjonsstatus, setAlleRefusjonsstatus] = useState( + undefined + ); + const { addAlert } = useContext(AlertContext); useEffect(() => { measureAll( (tidMs) => { @@ -129,18 +174,19 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { tidMs, }); }, - hentOrganisasjoner() - .then(orgs => { - const gyldigeOrganisasjoner = orgs.filter( - org => - org.OrganizationForm === 'BEDR' || - org.OrganizationForm === 'AAFY' || - org.Type === 'Enterprise', + hentUserInfo() + .then(({ organisasjoner, tilganger, altinnError }) => { + if (altinnError) { + setVisFeilmelding(true); + addAlert('TilgangerAltinn'); + } + setAltinnorganisasjoner(organisasjoner); + setAltinntilganger( + Record.fromEntries(tilganger.map((it) => [it.id, Set(it.organisasjoner)])) ); - setAltinnorganisasjoner(gyldigeOrganisasjoner); - if (gyldigeOrganisasjoner.length !== 0) { - hentAltinnRaporteeIdentiteter().then(result => { + if (organisasjoner.length !== 0) { + hentAltinnRaporteeIdentiteter().then((result) => { if (result instanceof Error) { autentiserAltinnBruker(window.location.href); setReporteeMessagesUrls({}); @@ -154,16 +200,11 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { }) .catch((error) => { Sentry.captureException(error); + setAltinntilganger(Record.map(altinntjeneste, () => Set())); setAltinnorganisasjoner([]); setVisFeilmelding(true); addAlert('TilgangerAltinn'); }), - hentAltinntilganger() - .then(setAltinntilganger) - .catch((error) => { - Sentry.captureException(error); - setAltinntilganger(Record.map(altinntjeneste, () => Set())); - }), hentAltinnTilgangssøknader() .then(setAltinnTilgangssøknader) .catch((error) => { @@ -171,9 +212,11 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { setAltinnTilgangssøknader([]); }), hentSyfoVirksomheter() - .then(virksomheter => { + .then((virksomheter) => { setSyfoVirksomheter(virksomheter); - setTilgangTilSyfo(virksomheter.length > 0 ? SyfoTilgang.TILGANG : SyfoTilgang.IKKE_TILGANG); + setTilgangTilSyfo( + virksomheter.length > 0 ? SyfoTilgang.TILGANG : SyfoTilgang.IKKE_TILGANG + ); amplitude.setUserProperties({ syfotilgang: virksomheter.length > 0 }); }) .catch((error) => { @@ -184,7 +227,7 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { addAlert('TilgangerDigiSyfo'); }), hentRefusjonstatus() - .then(refusjonstatus => { + .then((refusjonstatus) => { setAlleRefusjonsstatus(refusjonstatus); }) .catch((error) => { @@ -193,45 +236,50 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { // har ikke egen alert type på dette, da det mest sannsynlig er altinn som feiler setVisFeilmelding(true); addAlert('TilgangerAltinn'); - }), + }) ); }, []); - const beregnOrganisasjonerArgs = [ altinnorganisasjoner, syfoVirksomheter, altinntilganger, altinnTilgangssøknader, tilgangTilSyfo, - alleRefusjonsstatus - ] as const + alleRefusjonsstatus, + ] as const; const organisasjoner = useMemo( () => beregnOrganisasjoner(...beregnOrganisasjonerArgs), - beregnOrganisasjonerArgs, - ) + beregnOrganisasjonerArgs + ); const [organisasjonstreResponse, error] = useEffectfulAsyncFunction( undefined as OrganisasjonEnhet[] | undefined, byggOrganisasjonstre, [organisasjoner] - ) + ); const organisasjonstre = error ? [] : organisasjonstreResponse; const childrenMap = useMemo( - () => Map( - (organisasjonstre ?? []).map(({hovedenhet, underenheter}): [string, Set] => - [hovedenhet.OrganizationNumber, Set(underenheter.map(it => it.OrganizationNumber))] - ) - ), + () => + Map( + (organisasjonstre ?? []).map( + ({ hovedenhet, underenheter }): [string, Set] => [ + hovedenhet.OrganizationNumber, + Set(underenheter.map((it) => it.OrganizationNumber)), + ] + ) + ), [organisasjonstre] - ) + ); if (organisasjoner !== undefined && organisasjonstre !== undefined) { const detFinnesEnUnderenhetMedParent = () => { - return Record.values(organisasjoner).some(org => org.organisasjon.ParentOrganizationNumber); + return Record.values(organisasjoner).some( + (org) => org.organisasjon.ParentOrganizationNumber + ); }; const harTilganger = detFinnesEnUnderenhetMedParent() && Record.length(organisasjoner) > 0; @@ -252,9 +300,7 @@ export const OrganisasjonerOgTilgangerProvider: FunctionComponent = props => { ); } else { - return ( - - ); + return ; } }; @@ -262,26 +308,26 @@ const sjekkTilgangssøknader = ( orgnr: orgnr, id: AltinntjenesteId, _orgnrMedTilgang: Set, - altinnTilgangssøknader: AltinnTilgangssøknad[], + altinnTilgangssøknader: AltinnTilgangssøknad[] ): Søknadsstatus => { const { tjenestekode, tjenesteversjon } = altinntjeneste[id]; const søknader = altinnTilgangssøknader.filter( - s => + (s) => s.orgnr === orgnr && s.serviceCode === tjenestekode && - s.serviceEdition.toString() === tjenesteversjon, + s.serviceEdition.toString() === tjenesteversjon ); - if (søknader.some(_ => _.status === 'Unopened')) { + if (søknader.some((_) => _.status === 'Unopened')) { return { tilgang: 'søkt' }; } - const søknad = søknader.find(_ => _.status === 'Created'); + const søknad = søknader.find((_) => _.status === 'Created'); if (søknad) { return { tilgang: 'søknad opprettet', url: søknad.submitUrl }; } - if (søknader.some(_ => _.status === 'Accepted')) { + if (søknader.some((_) => _.status === 'Accepted')) { return { tilgang: 'godkjent' }; } return { tilgang: 'ikke søkt' }; diff --git a/src/altinn/organisasjon.ts b/src/altinn/organisasjon.ts index 8f2ce0c60..6d80b8a8e 100644 --- a/src/altinn/organisasjon.ts +++ b/src/altinn/organisasjon.ts @@ -1,4 +1,4 @@ -import {z} from "zod"; +import { z } from 'zod'; export const Organisasjon = z.object({ Name: z.string(), @@ -6,7 +6,11 @@ export const Organisasjon = z.object({ OrganizationNumber: z.string(), OrganizationForm: z.string(), Status: z.string(), - ParentOrganizationNumber: z.string().nullable().transform(o => o ?? ""), + ParentOrganizationNumber: z + .string() + .nullable() + .default('') + .transform((o) => o ?? ''), }); export type Organisasjon = z.infer; diff --git a/src/altinn/tilganger.ts b/src/altinn/tilganger.ts index f1f1ab0c9..287c2a343 100644 --- a/src/altinn/tilganger.ts +++ b/src/altinn/tilganger.ts @@ -1,37 +1,4 @@ -import { altinntjeneste, AltinnFellesInfo, AltinntjenesteId } from './tjenester'; -import * as Record from '../utils/Record'; -import { Organisasjon } from './organisasjon'; -import { Set } from 'immutable' - -type Orgnr = string; - -export const hentAltinntilganger = async (): Promise>> => { - const enkelttilganger = await Promise.all( - Record.mapToArray(altinntjeneste, hentAltinntilgangerForEnTjeneste) - ); - return Record.fromEntries(enkelttilganger); -}; - -const hentAltinntilgangerForEnTjeneste = async ( - id: AltinntjenesteId, - tjeneste: AltinnFellesInfo -): Promise<[AltinntjenesteId, Set]> => { - const respons = await fetch( - '/min-side-arbeidsgiver/api/rettigheter-til-skjema/?serviceKode=' + - tjeneste.tjenestekode + - '&serviceEdition=' + - tjeneste.tjenesteversjon - ); - - let organisasjoner: Organisasjon[] = []; - - if (respons.ok) { - organisasjoner = await respons.json(); - } - - const orgnr = organisasjoner.map(_ => _.OrganizationNumber); - return [id, Set(orgnr)]; -}; +import { altinntjeneste, AltinntjenesteId } from './tjenester'; const altinnTilgangssøknadUrl = '/min-side-arbeidsgiver/api/altinn-tilgangssoknad'; @@ -67,26 +34,28 @@ export interface AltinnTilgangssøknadskjemaDTO { serviceEdition: number; } -export const opprettAltinnTilgangssøknad = async (skjema: AltinnTilgangssøknadskjema): Promise => { +export const opprettAltinnTilgangssøknad = async ( + skjema: AltinnTilgangssøknadskjema +): Promise => { const dto: AltinnTilgangssøknadskjemaDTO = { orgnr: skjema.orgnr, redirectUrl: skjema.redirectUrl, serviceCode: altinntjeneste[skjema.altinnId].tjenestekode, - serviceEdition: parseInt(altinntjeneste[skjema.altinnId].tjenesteversjon) - } + serviceEdition: parseInt(altinntjeneste[skjema.altinnId].tjenesteversjon), + }; const response = await fetch(altinnTilgangssøknadUrl, { - 'method' : 'POST', + method: 'POST', body: JSON.stringify(dto), headers: { - 'content-type': 'application/json', - 'accept': 'application/json' - } + 'content-type': 'application/json', + accept: 'application/json', + }, }); if (!response.ok) { return null; } - return await response.json() as AltinnTilgangssøknad; + return (await response.json()) as AltinnTilgangssøknad; }; diff --git a/src/api/dnaApi.ts b/src/api/dnaApi.ts index 3b2ca96a6..43911c191 100644 --- a/src/api/dnaApi.ts +++ b/src/api/dnaApi.ts @@ -1,7 +1,7 @@ -import {Organisasjon} from '../altinn/organisasjon'; -import {z} from "zod"; -import * as Sentry from "@sentry/browser"; - +import { Organisasjon } from '../altinn/organisasjon'; +import { z } from 'zod'; +import * as Sentry from '@sentry/browser'; +import { AltinntjenesteId } from '../altinn/tjenester'; const digiSyfoVirksomheterURL = '/min-side-arbeidsgiver/api/narmesteleder/virksomheter-v3'; const DigiSyfoOrganisasjon = z.object({ @@ -17,16 +17,17 @@ export async function hentSyfoVirksomheter(): Promise; @@ -39,25 +40,41 @@ export async function hentRefusjonstatus(): Promise { try { return RefusjonStatusResponse.parse(data); } catch (error) { - Sentry.captureException(error) + Sentry.captureException(error); } } - throw new Error(`Kall til ${refusjonstatusURL} feilet med ${respons.status}:${respons.statusText}`); + throw new Error( + `Kall til ${refusjonstatusURL} feilet med ${respons.status}:${respons.statusText}` + ); } - const sjekkInnloggetURL = '/min-side-arbeidsgiver/api/innlogget'; export const sjekkInnlogget = async (): Promise => { - const {ok} = await fetch(sjekkInnloggetURL) - return ok -} + const { ok } = await fetch(sjekkInnloggetURL); + return ok; +}; -export async function hentOrganisasjoner(): Promise { - const respons = await fetch('/min-side-arbeidsgiver/api/organisasjoner'); +const UserInfoRespons = z.object({ + altinnError: z.boolean(), + organisasjoner: z.array(Organisasjon), + tilganger: z.array( + z.object({ + id: z.custom(), + tjenestekode: z.string(), + tjenesteversjon: z.string(), + organisasjoner: z.array(z.string()), + }) + ), +}); +export type UserInfo = z.infer; +export async function hentUserInfo(): Promise { + const respons = await fetch('/min-side-arbeidsgiver/api/userInfo/v1'); if (respons.ok) { - return await respons.json(); + return UserInfoRespons.parse(await respons.json()); } else { - throw new Error(`Kall til '/min-side-arbeidsgiver/api/organisasjoner' feilet med ${respons.status}:${respons.statusText}`); + throw new Error( + `Kall til '/min-side-arbeidsgiver/api/userInfo/v1' feilet med ${respons.status}:${respons.statusText}` + ); } } const storageUrl = `/min-side-arbeidsgiver/api/storage`; @@ -67,8 +84,8 @@ export async function getStorage(key: string): Promise { method: 'GET', headers: { 'Content-Type': 'application/json', - 'Accept': 'application/json', - } + Accept: 'application/json', + }, }); if (respons.status === 204) { return { @@ -76,7 +93,7 @@ export async function getStorage(key: string): Promise { key, data: [], version: respons.headers.get('version'), - } + }, }; } const jsonResult = await respons.json(); @@ -85,23 +102,30 @@ export async function getStorage(key: string): Promise { key, data: jsonResult, version: respons.headers.get('version'), - } + }, }; } catch (error) { - return {error}; + return { error }; } } -export async function putStorage(key: string, data: any, version: string | null = null): Promise { +export async function putStorage( + key: string, + data: any, + version: string | null = null +): Promise { try { - const respons = await fetch(`${storageUrl}/${key}${(version !== null ? `?version=${version}` : '')}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: JSON.stringify(data), - }); + const respons = await fetch( + `${storageUrl}/${key}${version !== null ? `?version=${version}` : ''}`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + body: JSON.stringify(data), + } + ); if (respons.status === 409) { return { @@ -114,38 +138,44 @@ export async function putStorage(key: string, data: any, version: string | null key, data, version, - } - } + }, + }; } else if (respons.ok) { return { updatedStorageItem: { key, data: await respons.json(), version: respons.headers.get('version'), - } + }, }; } else { return { error: { status: respons.status, statusText: respons.statusText, - } + }, }; } } catch (error) { - return {error}; + return { error }; } } -export async function deleteStorage(key: string, version: string | null = null): Promise { +export async function deleteStorage( + key: string, + version: string | null = null +): Promise { try { - const respons = await fetch(`${storageUrl}/${key}${(version !== null ? `?version=${version}` : '')}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - }); + const respons = await fetch( + `${storageUrl}/${key}${version !== null ? `?version=${version}` : ''}`, + { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + } + ); if (respons.status === 409) { return { currentStorageItem: { @@ -154,47 +184,51 @@ export async function deleteStorage(key: string, version: string | null = null): version: respons.headers.get('version'), }, rejectedStorageItem: null, - } + }; } else if (respons.ok) { return { deletedStorageItem: { key, data: await respons.json(), version: respons.headers.get('version'), - } + }, }; } else { return { error: { status: respons.status, statusText: respons.statusText, - } + }, }; } } catch (error) { - return {error}; + return { error }; } - } export type StorageItem = { - key: string, - data: any[], - version: string | null, -} + key: string; + data: any[]; + version: string | null; +}; export type StorageItemDeleted = { - deletedStorageItem: StorageItem, -} + deletedStorageItem: StorageItem; +}; export type StorageItemUpdated = { - updatedStorageItem: StorageItem, -} + updatedStorageItem: StorageItem; +}; export type StorageItemLoaded = { - loadedStorageItem: StorageItem, -} + loadedStorageItem: StorageItem; +}; export type StorageItemConflict = { - currentStorageItem: StorageItem, - rejectedStorageItem: StorageItem | null, -} + currentStorageItem: StorageItem; + rejectedStorageItem: StorageItem | null; +}; export type StorageError = { - error: any, -} -export type StorageItemResponse = StorageItemLoaded | StorageItemUpdated | StorageItemDeleted | StorageItemConflict | StorageError; \ No newline at end of file + error: any; +}; +export type StorageItemResponse = + | StorageItemLoaded + | StorageItemUpdated + | StorageItemDeleted + | StorageItemConflict + | StorageError; diff --git a/src/index.tsx b/src/index.tsx index 41babad43..ad61013a5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,7 +23,7 @@ class SentryDebugTransport implements SentryTypes.Transport { } sendEvent(event: SentryTypes.Event): PromiseLike { - console.error('would have sent to sentry', event); + console.error('would have sent to sentry', JSON.stringify(event, null, 2)); return Promise.resolve({ status: 'success' }); } } From d93a30af1239e86491ee57062a94c8a1bfca70fb Mon Sep 17 00:00:00 2001 From: ebelegu Date: Thu, 31 Aug 2023 15:40:14 +0200 Subject: [PATCH 07/25] haandterer undefined i hooks --- src/App/App.tsx | 257 ++++++++++-------- .../Arbeidsforholdboks/Arbeidsforholdboks.tsx | 11 +- .../useAntallArbeidsforholdFraAareg.ts | 61 +++++ .../ForebyggeFrav\303\246rboks.tsx" | 5 +- .../useSykefrav\303\246r.ts" | 36 +++ .../Kandidatlisteboks/Kandidatlisteboks.tsx | 6 +- .../Kandidatlisteboks/useAntallKandidater.ts | 40 +++ .../Tiltakboks/Tiltakboks.tsx | 22 +- .../Tiltakboks/useAvtaleoversikt.ts | 64 +++++ src/api/useAntallArbeidsforholdFraAareg.ts | 57 ---- src/api/useAntallKandidater.ts | 38 --- src/api/useArbeidsavtaler.ts | 35 --- "src/api/useSykefrav\303\246r.ts" | 33 --- 13 files changed, 352 insertions(+), 313 deletions(-) create mode 100644 src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts create mode 100644 "src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" create mode 100644 src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts create mode 100644 src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts delete mode 100644 src/api/useAntallArbeidsforholdFraAareg.ts delete mode 100644 src/api/useAntallKandidater.ts delete mode 100644 src/api/useArbeidsavtaler.ts delete mode 100644 "src/api/useSykefrav\303\246r.ts" diff --git a/src/App/App.tsx b/src/App/App.tsx index 048fe603b..d7416b114 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -18,6 +18,7 @@ import { SaksoversiktRestoreSession } from './Hovedside/Sak/Saksoversikt/Saksove import { Alert, Link } from '@navikt/ds-react'; import { gittMiljo } from '../utils/environment'; import Brodsmulesti from './Brodsmulesti/Brodsmulesti'; +import { SWRConfig } from 'swr'; const miljø = gittMiljo<'local' | 'labs' | 'dev' | 'prod'>({ prod: 'prod', @@ -56,127 +57,145 @@ const App: FunctionComponent = () => { return (
- - - - - - - - - - - - - - - } - /> - - - - } - /> - - - - } - /> - - - - - } - /> - - - - } - /> - - {' '} - Finner ikke siden.{' '} - + + + + + + + + + + + + + + + } + /> + + + + } + /> + + + + } + /> + + + + + } + /> + + + + } + /> + {' '} - Gå til Min side - arbeidsgiver - {' '} - - } - /> - - - - - - } - /> - - - - - + Finner ikke siden.{' '} + + {' '} + Gå til Min side + arbeidsgiver + {' '} + + } + /> + + + + + + } + /> + + + + + +
); }; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx index 9085a3747..1fe78054a 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx @@ -1,19 +1,12 @@ import React from 'react'; -import * as Sentry from '@sentry/browser'; import { innsynAaregURL } from '../../../../lenker'; import arbeidsforholdikon from './arbeidsforholdikon.svg'; -import { useAntallArbeidsforholdFraAareg } from '../../../../api/useAntallArbeidsforholdFraAareg'; +import { useAntallArbeidsforholdFraAareg } from './useAntallArbeidsforholdFraAareg'; import './ArbeidsforholdBoks.css'; import { Tjenesteboks } from '../Tjenesteboks'; const Arbeidsforholdboks = () => { - const { data, error } = useAntallArbeidsforholdFraAareg(); - - if (error !== undefined) { - return null; - } - - const antallArbeidsforhold = data?.second ?? 0; + const antallArbeidsforhold = useAntallArbeidsforholdFraAareg(); const orgnummerFraUrl = new URLSearchParams(window.location.search).get('bedrift') ?? ''; const href = innsynAaregURL + (orgnummerFraUrl === '' ? '' : `?bedrift=${orgnummerFraUrl}`); diff --git a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts new file mode 100644 index 000000000..b70f66866 --- /dev/null +++ b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts @@ -0,0 +1,61 @@ +import { z } from 'zod'; +import useSWR from 'swr'; +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import * as Sentry from '@sentry/browser'; +import { Severity } from '@sentry/react'; + +export const useAntallArbeidsforholdFraAareg = (): number => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + const { data } = useSWR( + valgtOrganisasjon !== undefined + ? { + url: '/min-side-arbeidsgiver/antall-arbeidsforhold', + jurenhet: valgtOrganisasjon.organisasjon.ParentOrganizationNumber ?? '', + orgnr: valgtOrganisasjon.organisasjon.OrganizationNumber, + } + : null, + fetcher + ); + + return data?.second ?? 0; +}; + +const Oversikt = z.object({ + second: z.number().optional(), +}); + +type AntallArbeidsforholdType = z.infer; + +const fetcher = async ({ + url, + jurenhet, + orgnr, +}: { + url: string; + jurenhet: string; + orgnr: string; +}) => { + const standardRespons: AntallArbeidsforholdType = { second: -1 }; + try { + const respons = await fetch(url, { + headers: { + jurenhet, + orgnr, + }, + }); + if (!respons.ok) { + Sentry.captureMessage( + `hent antall arbeidsforhold fra aareg feilet med ${respons.status}`, + Severity.Warning + ); + return standardRespons; + } + + const oversikt = Oversikt.parse(await respons.json()); + return oversikt.second === 0 ? standardRespons : oversikt; + } catch (error) { + Sentry.captureException(error); + return standardRespons; + } +}; diff --git "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" index 3a12c0dc4..6c415c958 100644 --- "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" +++ "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" @@ -1,9 +1,8 @@ import { lenkeTilForebyggefravar } from '../../../../lenker'; import React from 'react'; -import * as Sentry from '@sentry/browser'; import ForebyggeFraværIkon from './ForebyggeFraværIkon.svg'; import './ForebyggeFraværboks.css'; -import { useSykefravær } from '../../../../api/useSykefravær'; +import { useSykefravær } from './useSykefravær'; import { StortTall, Tjenesteboks } from '../Tjenesteboks'; const ForebyggeFraværboks = () => { @@ -37,7 +36,7 @@ const Beskrivelse = () => { } }; - const { data: sykefravær } = useSykefravær(); + const sykefravær = useSykefravær(); if (sykefravær !== undefined) { return ( diff --git "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" new file mode 100644 index 000000000..13a3cb7eb --- /dev/null +++ "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" @@ -0,0 +1,36 @@ +import { z } from 'zod'; +import useSWR from 'swr'; +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import * as Sentry from '@sentry/browser'; + +const Sykefraværsrespons = z.object({ + type: z.string(), + label: z.string(), + prosent: z.number(), +}); + +export type Sykefraværsrespons = z.infer; + +export const useSykefravær = (): Sykefraværsrespons | undefined => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + + const { data } = useSWR( + valgtOrganisasjon !== undefined + ? `/min-side-arbeidsgiver/api/sykefravaerstatistikk/${valgtOrganisasjon.organisasjon.OrganizationNumber}` + : null, + fetcher + ); + + return data; +}; + +const fetcher = async (url: string) => { + try { + const respons = await fetch(url); + return Sykefraværsrespons.parse(await respons.json()); + } catch (error) { + Sentry.captureException(error); + return undefined; + } +}; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx index 25e50c381..f411b5e44 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx @@ -1,14 +1,12 @@ import React from 'react'; import { kandidatlisteURL } from '../../../../lenker'; import { Tjenesteboks } from '../Tjenesteboks'; -import { useAntallKandidater } from '../../../../api/useAntallKandidater'; +import { useAntallKandidater } from './useAntallKandidater'; import ikon from './kandidatlisteboks-ikon.svg'; import './Kandidatlisteboks.css'; const Kandidatlisteboks = () => { - const { data } = useAntallKandidater(); - - const antallKandidater = data?.antallKandidater ?? 0; + const antallKandidater = useAntallKandidater(); const orgnummerFraUrl = new URLSearchParams(window.location.search).get('bedrift') ?? ''; const href = diff --git a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts new file mode 100644 index 000000000..a1e798208 --- /dev/null +++ b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts @@ -0,0 +1,40 @@ +import { z } from 'zod'; +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import useSWR from 'swr'; +import * as Sentry from '@sentry/browser'; +import { Severity } from '@sentry/react'; + +export const useAntallKandidater = (): number => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + + const { data } = useSWR( + valgtOrganisasjon !== undefined + ? `/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern/antallkandidater?virksomhetsnummer=${valgtOrganisasjon.organisasjon.OrganizationNumber}` + : null, + fetcher + ); + + return data?.antallKandidater ?? 0; +}; + +const PresenterteKandidater = z.object({ + antallKandidater: z.number(), +}); + +const fetcher = async (url: string) => { + try { + const respons = await fetch(url); + if (!respons.ok) { + Sentry.captureMessage( + `hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}`, + Severity.Warning + ); + return { antallKandidater: 0 }; + } + return PresenterteKandidater.parse(await respons.json()); + } catch (error) { + Sentry.captureException(error); + return { antallKandidater: 0 }; + } +}; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx index 8d7ecd9ac..55b1d69bc 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx @@ -1,15 +1,13 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useContext } from 'react'; import { tiltaksgjennomforingURL } from '../../../../lenker'; import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; import './Tiltakboks.css'; import tiltakikon from './tiltakboks-ikon.svg'; -import { Arbeidsavtale, useArbeidsavtaler } from '../../../../api/useArbeidsavtaler'; +import { Avtalenavn, useAvtaleoversikt } from './useAvtaleoversikt'; import { Tjenesteboks } from '../Tjenesteboks'; import { BodyShort } from '@navikt/ds-react'; -import * as Record from '../../../../utils/Record'; -import * as Sentry from '@sentry/browser'; -const displayname = { +const displayname: Record = { ARBEIDSTRENING: 'arbeidstrening', MIDLERTIDIG_LONNSTILSKUDD: 'lønnstilskudd', VARIG_LONNSTILSKUDD: 'varig lønnstilskudd', @@ -18,7 +16,7 @@ const displayname = { MENTOR: 'mentortilskudd', }; -const displayorder: (keyof typeof displayname)[] = [ +const displayorder: Avtalenavn[] = [ 'ARBEIDSTRENING', 'MIDLERTIDIG_LONNSTILSKUDD', 'VARIG_LONNSTILSKUDD', @@ -31,18 +29,12 @@ const Tiltakboks = () => { const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); const orgnr = valgtOrganisasjon?.organisasjon?.OrganizationNumber; - const { data: avtaler = [] } = useArbeidsavtaler(); + const avtaler = useAvtaleoversikt(); - const avtalerMedTiltaktype = (tiltaktype: string) => - avtaler.filter((avtale: Arbeidsavtale) => avtale.tiltakstype === tiltaktype).length; - - const tiltakUrl = - orgnr !== undefined && orgnr !== '' - ? `${tiltaksgjennomforingURL}&bedrift=${orgnr}` - : tiltaksgjennomforingURL; + const tiltakUrl = `${tiltaksgjennomforingURL}&bedrift=${orgnr ?? ''}`; const tallElems = displayorder.flatMap((avtaletype) => { - const antall = avtalerMedTiltaktype(avtaletype); + const antall = avtaler[avtaletype]; return antall > 0 ? [
diff --git a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts new file mode 100644 index 000000000..70d092c53 --- /dev/null +++ b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts @@ -0,0 +1,64 @@ +import { useContext, useMemo } from 'react'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import useSWR from 'swr'; +import { z } from 'zod'; +import * as Sentry from '@sentry/browser'; +import { count } from '../../../../utils/util'; + +export type Avtaleoversikt = { + ARBEIDSTRENING: number; + MIDLERTIDIG_LONNSTILSKUDD: number; + VARIG_LONNSTILSKUDD: number; + SOMMERJOBB: number; + INKLUDERINGSTILSKUDD: number; + MENTOR: number; +}; + +export type Avtalenavn = keyof Avtaleoversikt; + +export const useAvtaleoversikt = (): Avtaleoversikt => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + const { data: avtaler = [] } = useSWR( + valgtOrganisasjon !== undefined + ? `/min-side-arbeidsgiver/tiltaksgjennomforing-api/avtaler/min-side-arbeidsgiver?bedriftNr=${valgtOrganisasjon.organisasjon.OrganizationNumber}` + : null, + fetcher + ); + + return useMemo( + () => ({ + ARBEIDSTRENING: antallAvtaler(avtaler, 'ARBEIDSTRENING'), + MIDLERTIDIG_LONNSTILSKUDD: antallAvtaler(avtaler, 'MIDLERTIDIG_LONNSTILSKUDD'), + VARIG_LONNSTILSKUDD: antallAvtaler(avtaler, 'VARIG_LONNSTILSKUDD'), + SOMMERJOBB: antallAvtaler(avtaler, 'SOMMERJOBB'), + INKLUDERINGSTILSKUDD: antallAvtaler(avtaler, 'INKLUDERINGSTILSKUDD'), + MENTOR: antallAvtaler(avtaler, 'MENTOR'), + }), + [avtaler] + ); +}; + +const antallAvtaler = (avtaler: Arbeidsavtale[], tiltaktype: Avtalenavn) => + count(avtaler, (it) => it.tiltakstype === tiltaktype); + +interface Arbeidsavtale { + tiltakstype: string; +} + +const arbeidsavtaleResponsType = z.array( + z.object({ + tiltakstype: z.string(), + }) +); + +const fetcher = async (url: string) => { + try { + const respons = await fetch(url); + if (respons.ok) { + return arbeidsavtaleResponsType.parse(await respons.json()); + } + } catch (error) { + Sentry.captureException(error); + } + return arbeidsavtaleResponsType.parse([]); +}; diff --git a/src/api/useAntallArbeidsforholdFraAareg.ts b/src/api/useAntallArbeidsforholdFraAareg.ts deleted file mode 100644 index a9d369ab3..000000000 --- a/src/api/useAntallArbeidsforholdFraAareg.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { z } from 'zod'; -import useSWR, { SWRResponse } from 'swr'; -import { useContext } from 'react'; -import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; -import * as Sentry from '@sentry/browser'; -import { Severity } from '@sentry/react'; - -const Oversikt = z.object({ - second: z.number().optional(), -}); - -export type AntallArbeidsforholdType = z.infer; - -export const useAntallArbeidsforholdFraAareg = (): SWRResponse => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - - const arbeidsgiverURL = '/min-side-arbeidsgiver/antall-arbeidsforhold'; - const headers = { - headers: - valgtOrganisasjon !== undefined - ? { - jurenhet: valgtOrganisasjon.organisasjon.ParentOrganizationNumber ?? '', - orgnr: valgtOrganisasjon.organisasjon.OrganizationNumber, - } - : {}, - }; - - const fetcher = async (url: string, headers: {}) => { - const standardRespons: AntallArbeidsforholdType = { second: -1 }; - const respons = await fetch(url, headers); - if (!respons.ok) { - Sentry.captureMessage( - `hent antall arbeidsforhold fra aareg feilet med ${respons.status}`, - Severity.Warning - ); - return standardRespons; - } - try { - const oversikt = Oversikt.parse(await respons.json()); - return oversikt.second === 0 ? standardRespons : oversikt; - } catch (error) { - Sentry.captureException(error); - return standardRespons; - } - }; - - const respons = useSWR( - valgtOrganisasjon !== undefined ? [arbeidsgiverURL, headers] : null, - ([url, headers]) => fetcher(url, headers) - ); - - const { error } = respons; - if (error !== undefined) { - Sentry.captureException(error); - } - return respons; -}; diff --git a/src/api/useAntallKandidater.ts b/src/api/useAntallKandidater.ts deleted file mode 100644 index 94de40231..000000000 --- a/src/api/useAntallKandidater.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { z } from 'zod'; -import { useContext } from 'react'; -import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; -import useSWR, { SWRResponse } from 'swr'; -import * as Sentry from '@sentry/browser'; -import { Severity } from '@sentry/react'; - -const PresenterteKandidater = z.object({ - antallKandidater: z.number(), -}); - -type PresenterteKandidater = z.infer; - -export const useAntallKandidater = (): SWRResponse => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - - const orgnr = valgtOrganisasjon?.organisasjon.OrganizationNumber; - const url = `/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern/antallkandidater?virksomhetsnummer=${orgnr}`; - - const fetcher = async (url: string) => { - const respons = await fetch(url); - if (!respons.ok) { - Sentry.captureMessage( - `hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}`, - Severity.Warning - ); - return { antallKandidater: 0 }; - } - try { - return PresenterteKandidater.parse(await respons.json()); - } catch (error) { - Sentry.captureException(error); - return { antallKandidater: 0 }; - } - }; - - return useSWR(valgtOrganisasjon !== undefined ? url : null, fetcher); -}; diff --git a/src/api/useArbeidsavtaler.ts b/src/api/useArbeidsavtaler.ts deleted file mode 100644 index 9296a6347..000000000 --- a/src/api/useArbeidsavtaler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useContext } from 'react'; -import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; -import useSWR, { SWRResponse } from 'swr'; -import { z } from 'zod'; -import * as Sentry from '@sentry/browser'; - -export interface Arbeidsavtale { - tiltakstype: string; -} - -const arbeidsavtaleResponsType = z.array( - z.object({ - tiltakstype: z.string(), - }) -); - -export const useArbeidsavtaler = (): SWRResponse, any> => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - const orgnr = valgtOrganisasjon?.organisasjon?.OrganizationNumber; - - const url = `/min-side-arbeidsgiver/tiltaksgjennomforing-api/avtaler/min-side-arbeidsgiver?bedriftNr=${orgnr}`; - const fetcher = async (url: string) => { - const respons = await fetch(url); - return arbeidsavtaleResponsType.parse(await respons.json()); - }; - - const respons = useSWR(valgtOrganisasjon !== undefined ? url : null, fetcher); - const { error } = respons; - - if (error !== undefined) { - Sentry.captureException(error); - } - - return respons; -}; diff --git "a/src/api/useSykefrav\303\246r.ts" "b/src/api/useSykefrav\303\246r.ts" deleted file mode 100644 index 8d836f552..000000000 --- "a/src/api/useSykefrav\303\246r.ts" +++ /dev/null @@ -1,33 +0,0 @@ -import { z } from 'zod'; -import useSWR, { SWRResponse } from 'swr'; -import { useContext } from 'react'; -import { OrganisasjonsDetaljerContext } from '../App/OrganisasjonDetaljerProvider'; -import * as Sentry from '@sentry/browser'; - -const Sykefraværsrespons = z.object({ - type: z.string(), - label: z.string(), - prosent: z.number(), -}); -export type Sykefraværsrespons = z.infer; - -export const useSykefravær = (): SWRResponse => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - const orgnr = valgtOrganisasjon?.organisasjon.OrganizationNumber; - - const url = `/min-side-arbeidsgiver/api/sykefravaerstatistikk/${orgnr}`; - - const fetcher = async (url: string) => { - const respons = await fetch(url); - return Sykefraværsrespons.parse(await respons.json()); - }; - - const respons = useSWR(valgtOrganisasjon !== undefined ? url : null, fetcher); - const { error } = respons; - - if (error !== undefined) { - Sentry.captureException(error); - } - - return respons; -}; From 6421639bd9f4875853ac883170133a7e48cc7c07 Mon Sep 17 00:00:00 2001 From: ebelegu Date: Thu, 31 Aug 2023 15:59:09 +0200 Subject: [PATCH 08/25] ikke stringify sentry feil lokalt/dev --- src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index ad61013a5..41babad43 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,7 +23,7 @@ class SentryDebugTransport implements SentryTypes.Transport { } sendEvent(event: SentryTypes.Event): PromiseLike { - console.error('would have sent to sentry', JSON.stringify(event, null, 2)); + console.error('would have sent to sentry', event); return Promise.resolve({ status: 'success' }); } } From d606c66ca1d137f21a00e4e87cd1eea5284d8d34 Mon Sep 17 00:00:00 2001 From: Peter Brottveit Bock Date: Thu, 31 Aug 2023 16:19:58 +0200 Subject: [PATCH 09/25] Revert "Swr og zod for tjenestebokser" --- package-lock.json | 46 ---- package.json | 1 - src/App/App.tsx | 257 ++++++++---------- .../Arbeidsforholdboks/Arbeidsforholdboks.tsx | 24 +- .../useAntallArbeidsforholdFraAareg.ts | 61 ----- .../ForebyggeFrav\303\246rboks.tsx" | 65 +++-- .../useSykefrav\303\246r.ts" | 36 --- .../Kandidatlisteboks/Kandidatlisteboks.tsx | 45 +-- .../Kandidatlisteboks/useAntallKandidater.ts | 40 --- .../Tiltakboks/Tiltakboks.tsx | 122 +++++---- .../Tiltakboks/useAvtaleoversikt.ts | 64 ----- src/api/arbeidsavtalerApi.ts | 13 + src/api/arbeidsforholdApi.ts | 35 +++ src/api/presenterteKandidaterApi.ts | 29 ++ "src/api/sykefrav\303\246rStatistikkApi.ts" | 27 ++ src/index.tsx | 2 +- 16 files changed, 371 insertions(+), 496 deletions(-) delete mode 100644 src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts delete mode 100644 "src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" delete mode 100644 src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts delete mode 100644 src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts create mode 100644 src/api/arbeidsavtalerApi.ts create mode 100644 src/api/arbeidsforholdApi.ts create mode 100644 src/api/presenterteKandidaterApi.ts create mode 100644 "src/api/sykefrav\303\246rStatistikkApi.ts" diff --git a/package-lock.json b/package-lock.json index 128a1f32c..a2ffdeaa3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,6 @@ "react-tooltip": "^4.2.13", "regenerator-runtime": "^0.13.7", "smoothscroll-polyfill": "^0.4.4", - "swr": "^2.2.2", "typescript": "4.5.4", "uuid": "^9.0.0", "whatwg-fetch": "^3.6.2", @@ -7289,11 +7288,6 @@ "node": ">= 10" } }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" - }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -18608,18 +18602,6 @@ "tslib": "^2.0.3" } }, - "node_modules/swr": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.2.tgz", - "integrity": "sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==", - "dependencies": { - "client-only": "^0.0.1", - "use-sync-external-store": "^1.2.0" - }, - "peerDependencies": { - "react": "^16.11.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -19345,14 +19327,6 @@ "braces": "^3.0.2" } }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -25727,11 +25701,6 @@ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, - "client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -33913,15 +33882,6 @@ "tslib": "^2.0.3" } }, - "swr": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.2.tgz", - "integrity": "sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==", - "requires": { - "client-only": "^0.0.1", - "use-sync-external-store": "^1.2.0" - } - }, "symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -34442,12 +34402,6 @@ "braces": "^3.0.2" } }, - "use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "requires": {} - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 06df93f10..b9d95f091 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "react-tooltip": "^4.2.13", "regenerator-runtime": "^0.13.7", "smoothscroll-polyfill": "^0.4.4", - "swr": "^2.2.2", "typescript": "4.5.4", "uuid": "^9.0.0", "whatwg-fetch": "^3.6.2", diff --git a/src/App/App.tsx b/src/App/App.tsx index d7416b114..048fe603b 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -18,7 +18,6 @@ import { SaksoversiktRestoreSession } from './Hovedside/Sak/Saksoversikt/Saksove import { Alert, Link } from '@navikt/ds-react'; import { gittMiljo } from '../utils/environment'; import Brodsmulesti from './Brodsmulesti/Brodsmulesti'; -import { SWRConfig } from 'swr'; const miljø = gittMiljo<'local' | 'labs' | 'dev' | 'prod'>({ prod: 'prod', @@ -57,145 +56,127 @@ const App: FunctionComponent = () => { return (
- - - - - - - - - - - - - - - - } - /> - - - - } - /> - - - - } - /> - - - - - } - /> - - - - } - /> - + + + + + + + + + + + + + + } + /> + + + + } + /> + + + + } + /> + + + + + } + /> + + + + } + /> + + {' '} + Finner ikke siden.{' '} + {' '} - Finner ikke siden.{' '} - - {' '} - Gå til Min side - arbeidsgiver - {' '} - - } - /> - - - - - - } - /> - - - - - - + Gå til Min side + arbeidsgiver + {' '} + + } + /> + + + + + + } + /> + + + + +
); }; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx index 1fe78054a..f81c0f01d 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx @@ -1,13 +1,25 @@ -import React from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { innsynAaregURL } from '../../../../lenker'; import arbeidsforholdikon from './arbeidsforholdikon.svg'; -import { useAntallArbeidsforholdFraAareg } from './useAntallArbeidsforholdFraAareg'; +import { hentAntallArbeidsforholdFraAareg } from '../../../../api/arbeidsforholdApi'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; import './ArbeidsforholdBoks.css'; import { Tjenesteboks } from '../Tjenesteboks'; const Arbeidsforholdboks = () => { - const antallArbeidsforhold = useAntallArbeidsforholdFraAareg(); - + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + const [antallArbeidsforhold, setAntallArbeidsforhold] = useState('–'); + useEffect(() => { + if (valgtOrganisasjon) + hentAntallArbeidsforholdFraAareg( + valgtOrganisasjon.organisasjon.OrganizationNumber, + valgtOrganisasjon.organisasjon.ParentOrganizationNumber ?? '' + ).then((antallArbeidsforholdRespons) => + setAntallArbeidsforhold( + antallArbeidsforholdRespons > 0 ? antallArbeidsforholdRespons.toString() : '–' + ) + ); + }, [valgtOrganisasjon]); const orgnummerFraUrl = new URLSearchParams(window.location.search).get('bedrift') ?? ''; const href = innsynAaregURL + (orgnummerFraUrl === '' ? '' : `?bedrift=${orgnummerFraUrl}`); @@ -21,9 +33,7 @@ const Arbeidsforholdboks = () => {
{' '} - - {antallArbeidsforhold > 0 ? antallArbeidsforhold : '-'} - + {antallArbeidsforhold} arbeidsforhold (aktive og avsluttede){' '}
diff --git a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts deleted file mode 100644 index b70f66866..000000000 --- a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { z } from 'zod'; -import useSWR from 'swr'; -import { useContext } from 'react'; -import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; -import * as Sentry from '@sentry/browser'; -import { Severity } from '@sentry/react'; - -export const useAntallArbeidsforholdFraAareg = (): number => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - const { data } = useSWR( - valgtOrganisasjon !== undefined - ? { - url: '/min-side-arbeidsgiver/antall-arbeidsforhold', - jurenhet: valgtOrganisasjon.organisasjon.ParentOrganizationNumber ?? '', - orgnr: valgtOrganisasjon.organisasjon.OrganizationNumber, - } - : null, - fetcher - ); - - return data?.second ?? 0; -}; - -const Oversikt = z.object({ - second: z.number().optional(), -}); - -type AntallArbeidsforholdType = z.infer; - -const fetcher = async ({ - url, - jurenhet, - orgnr, -}: { - url: string; - jurenhet: string; - orgnr: string; -}) => { - const standardRespons: AntallArbeidsforholdType = { second: -1 }; - try { - const respons = await fetch(url, { - headers: { - jurenhet, - orgnr, - }, - }); - if (!respons.ok) { - Sentry.captureMessage( - `hent antall arbeidsforhold fra aareg feilet med ${respons.status}`, - Severity.Warning - ); - return standardRespons; - } - - const oversikt = Oversikt.parse(await respons.json()); - return oversikt.second === 0 ? standardRespons : oversikt; - } catch (error) { - Sentry.captureException(error); - return standardRespons; - } -}; diff --git "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" index 6c415c958..a48757609 100644 --- "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" +++ "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" @@ -1,9 +1,12 @@ -import { lenkeTilForebyggefravar } from '../../../../lenker'; -import React from 'react'; +import {lenkeTilForebyggefravar} from '../../../../lenker'; +import React, {useContext, useEffect, useState} from 'react'; +import * as Sentry from "@sentry/browser"; import ForebyggeFraværIkon from './ForebyggeFraværIkon.svg'; import './ForebyggeFraværboks.css'; -import { useSykefravær } from './useSykefravær'; -import { StortTall, Tjenesteboks } from '../Tjenesteboks'; +import {OrganisasjonsDetaljerContext} from '../../../OrganisasjonDetaljerProvider'; +import {hentSykefravær, Sykefraværsrespons} from '../../../../api/sykefraværStatistikkApi'; +import {StortTall, Tjenesteboks} from "../Tjenesteboks"; + const ForebyggeFraværboks = () => { const valgtbedrift = () => { @@ -11,46 +14,52 @@ const ForebyggeFraværboks = () => { return orgnummerFraUrl === '' ? '' : `?bedrift=${orgnummerFraUrl}`; }; - return ( - - - - ); + return + + ; }; -const beskrivelse = 'Verktøy for å forebygge fravær i din virksomhet.'; +const beskrivelse = 'Verktøy for å forebygge fravær i din virksomhet.' const Beskrivelse = () => { + const {valgtOrganisasjon} = useContext(OrganisasjonsDetaljerContext); + const [sykefravær, setSykefravær] = useState(undefined); + const statistikktype = (type: string) => { switch (type) { case 'NÆRING': case 'BRANSJE': - return 'bransje'; - default: - return 'bedrift'; + return 'bransje' + default : + return 'bedrift' } - }; - - const sykefravær = useSykefravær(); + } + useEffect(() => { + if (valgtOrganisasjon) + hentSykefravær(valgtOrganisasjon.organisasjon.OrganizationNumber).then(sykefraværsrespons => + setSykefravær(sykefraværsrespons), + ).catch(error => { + Sentry.captureException(error) + setSykefravær(undefined); + }); + }, [valgtOrganisasjon]); if (sykefravær !== undefined) { return ( - {sykefravær.prosent.toString()} % - <> - {' '} - legemeldt sykefravær i din {statistikktype(sykefravær.type)}. Lag en plan for å - redusere fraværet.{' '} - + + {sykefravær.prosent.toString()} % + + <> legemeldt sykefravær i din {statistikktype(sykefravær.type)}. Lag en plan for å redusere fraværet. ); } return {beskrivelse}; -}; +} export default ForebyggeFraværboks; diff --git "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" deleted file mode 100644 index 13a3cb7eb..000000000 --- "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" +++ /dev/null @@ -1,36 +0,0 @@ -import { z } from 'zod'; -import useSWR from 'swr'; -import { useContext } from 'react'; -import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; -import * as Sentry from '@sentry/browser'; - -const Sykefraværsrespons = z.object({ - type: z.string(), - label: z.string(), - prosent: z.number(), -}); - -export type Sykefraværsrespons = z.infer; - -export const useSykefravær = (): Sykefraværsrespons | undefined => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - - const { data } = useSWR( - valgtOrganisasjon !== undefined - ? `/min-side-arbeidsgiver/api/sykefravaerstatistikk/${valgtOrganisasjon.organisasjon.OrganizationNumber}` - : null, - fetcher - ); - - return data; -}; - -const fetcher = async (url: string) => { - try { - const respons = await fetch(url); - return Sykefraværsrespons.parse(await respons.json()); - } catch (error) { - Sentry.captureException(error); - return undefined; - } -}; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx index f411b5e44..3c0cbf1e7 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx @@ -1,35 +1,36 @@ -import React from 'react'; -import { kandidatlisteURL } from '../../../../lenker'; -import { Tjenesteboks } from '../Tjenesteboks'; -import { useAntallKandidater } from './useAntallKandidater'; -import ikon from './kandidatlisteboks-ikon.svg'; +import React, {useContext, useEffect, useState} from 'react'; +import {OrganisasjonsDetaljerContext} from '../../../OrganisasjonDetaljerProvider'; +import {kandidatlisteURL} from '../../../../lenker'; +import {Tjenesteboks} from "../Tjenesteboks"; +import {hentAntallKandidater} from "../../../../api/presenterteKandidaterApi"; +import ikon from "./kandidatlisteboks-ikon.svg"; import './Kandidatlisteboks.css'; const Kandidatlisteboks = () => { - const antallKandidater = useAntallKandidater(); - + const {valgtOrganisasjon} = useContext(OrganisasjonsDetaljerContext); + const [antallKandidater, setantallKandidater] = useState(0); + useEffect(() => { + if (valgtOrganisasjon) + hentAntallKandidater(valgtOrganisasjon.organisasjon.OrganizationNumber).then(antallKandidater => + setantallKandidater(antallKandidater) + ); + }, [valgtOrganisasjon]); const orgnummerFraUrl = new URLSearchParams(window.location.search).get('bedrift') ?? ''; - const href = - kandidatlisteURL + (orgnummerFraUrl === '' ? '' : `?virksomhet=${orgnummerFraUrl}`); + const href = kandidatlisteURL + (orgnummerFraUrl === '' ? '' : `?virksomhet=${orgnummerFraUrl}`); - return antallKandidater === 0 ? null : ( - -
- - {' '} - - {antallKandidater} - kandidater{' '} - -
+
+ {antallKandidater}kandidater +
- ); }; export default Kandidatlisteboks; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts deleted file mode 100644 index a1e798208..000000000 --- a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { z } from 'zod'; -import { useContext } from 'react'; -import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; -import useSWR from 'swr'; -import * as Sentry from '@sentry/browser'; -import { Severity } from '@sentry/react'; - -export const useAntallKandidater = (): number => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - - const { data } = useSWR( - valgtOrganisasjon !== undefined - ? `/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern/antallkandidater?virksomhetsnummer=${valgtOrganisasjon.organisasjon.OrganizationNumber}` - : null, - fetcher - ); - - return data?.antallKandidater ?? 0; -}; - -const PresenterteKandidater = z.object({ - antallKandidater: z.number(), -}); - -const fetcher = async (url: string) => { - try { - const respons = await fetch(url); - if (!respons.ok) { - Sentry.captureMessage( - `hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}`, - Severity.Warning - ); - return { antallKandidater: 0 }; - } - return PresenterteKandidater.parse(await respons.json()); - } catch (error) { - Sentry.captureException(error); - return { antallKandidater: 0 }; - } -}; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx index 55b1d69bc..879275590 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx @@ -1,80 +1,98 @@ -import React, { useContext } from 'react'; -import { tiltaksgjennomforingURL } from '../../../../lenker'; -import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import React, {useContext, useEffect, useState} from 'react'; +import {tiltaksgjennomforingURL} from '../../../../lenker'; +import {OrganisasjonsDetaljerContext} from '../../../OrganisasjonDetaljerProvider'; import './Tiltakboks.css'; import tiltakikon from './tiltakboks-ikon.svg'; -import { Avtalenavn, useAvtaleoversikt } from './useAvtaleoversikt'; -import { Tjenesteboks } from '../Tjenesteboks'; -import { BodyShort } from '@navikt/ds-react'; +import {Arbeidsavtale, hentArbeidsavtaler} from '../../../../api/arbeidsavtalerApi'; +import {Tjenesteboks} from "../Tjenesteboks"; +import {BodyShort} from "@navikt/ds-react"; +import * as Record from '../../../../utils/Record' -const displayname: Record = { - ARBEIDSTRENING: 'arbeidstrening', - MIDLERTIDIG_LONNSTILSKUDD: 'lønnstilskudd', - VARIG_LONNSTILSKUDD: 'varig lønnstilskudd', - SOMMERJOBB: 'sommerjobb', - INKLUDERINGSTILSKUDD: 'inkluderingstilskudd', - MENTOR: 'mentortilskudd', -}; +const displayname = { + 'ARBEIDSTRENING': 'arbeidstrening', + 'MIDLERTIDIG_LONNSTILSKUDD': 'lønnstilskudd', + 'VARIG_LONNSTILSKUDD': 'varig lønnstilskudd', + 'SOMMERJOBB': 'sommerjobb', + 'INKLUDERINGSTILSKUDD': 'inkluderingstilskudd', + 'MENTOR': 'mentortilskudd', +} -const displayorder: Avtalenavn[] = [ +const displayorder: (keyof typeof displayname)[] = [ 'ARBEIDSTRENING', 'MIDLERTIDIG_LONNSTILSKUDD', 'VARIG_LONNSTILSKUDD', 'SOMMERJOBB', 'INKLUDERINGSTILSKUDD', 'MENTOR', -]; +] const Tiltakboks = () => { const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - const orgnr = valgtOrganisasjon?.organisasjon?.OrganizationNumber; + const orgnr = valgtOrganisasjon?.organisasjon?.OrganizationNumber + const [avtaleoversikt, setAvtaleoversikt] = useState>({}) - const avtaler = useAvtaleoversikt(); + useEffect(() => { + if (orgnr !== undefined) { + hentArbeidsavtaler(orgnr) + .then((avtaler: Arbeidsavtale[]) => { + const avtalerMedTiltaktype = (tiltaktype: string) => + avtaler.filter((avtale: Arbeidsavtale) => avtale.tiltakstype === tiltaktype).length; + setAvtaleoversikt({ + 'ARBEIDSTRENING': avtalerMedTiltaktype('ARBEIDSTRENING'), + 'MIDLERTIDIG_LONNSTILSKUDD': avtalerMedTiltaktype('MIDLERTIDIG_LONNSTILSKUDD'), + 'VARIG_LONNSTILSKUDD': avtalerMedTiltaktype('VARIG_LONNSTILSKUDD'), + 'SOMMERJOBB': avtalerMedTiltaktype('SOMMERJOBB'), + 'INKLUDERINGSTILSKUDD': avtalerMedTiltaktype('INKLUDERINGSTILSKUDD'), + 'MENTOR': avtalerMedTiltaktype('MENTOR'), + }) + }) + .catch(_ => { + setAvtaleoversikt({}) + }); + } + }, [orgnr]); - const tiltakUrl = `${tiltaksgjennomforingURL}&bedrift=${orgnr ?? ''}`; + const tiltakUrl = orgnr !== undefined && orgnr !== '' + ? `${tiltaksgjennomforingURL}&bedrift=${orgnr}` + : tiltaksgjennomforingURL; - const tallElems = displayorder.flatMap((avtaletype) => { - const antall = avtaler[avtaletype]; + const tallElems = displayorder.flatMap(avtaletype => { + const antall = avtaleoversikt[avtaletype] ?? 0; return antall > 0 ? [ -
- {antall} -
, -
- {displayname[avtaletype]} -
, - ] +
{antall}
, +
{displayname[avtaletype]}
, + ] : []; - }); + }) - return ( - +
+ {tallElems.length > 0 + ?
+ { tallElems } +
+ : } - > -
- {tallElems.length > 0 ? ( -
{tallElems}
- ) : ( - - )} -
- - ); +
+
; }; -const TekstUtenTall = () => ( +const TekstUtenTall = () => <> - + Arbeidstrening, lønnstilskudd, mentortilskudd, inkluderingstilskudd og sommerjobb. - De ulike tiltakene krever egne tilganger i Altinn - -); + + De ulike tiltakene krever egne tilganger i Altinn + + ; + export default Tiltakboks; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts deleted file mode 100644 index 70d092c53..000000000 --- a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useContext, useMemo } from 'react'; -import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; -import useSWR from 'swr'; -import { z } from 'zod'; -import * as Sentry from '@sentry/browser'; -import { count } from '../../../../utils/util'; - -export type Avtaleoversikt = { - ARBEIDSTRENING: number; - MIDLERTIDIG_LONNSTILSKUDD: number; - VARIG_LONNSTILSKUDD: number; - SOMMERJOBB: number; - INKLUDERINGSTILSKUDD: number; - MENTOR: number; -}; - -export type Avtalenavn = keyof Avtaleoversikt; - -export const useAvtaleoversikt = (): Avtaleoversikt => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - const { data: avtaler = [] } = useSWR( - valgtOrganisasjon !== undefined - ? `/min-side-arbeidsgiver/tiltaksgjennomforing-api/avtaler/min-side-arbeidsgiver?bedriftNr=${valgtOrganisasjon.organisasjon.OrganizationNumber}` - : null, - fetcher - ); - - return useMemo( - () => ({ - ARBEIDSTRENING: antallAvtaler(avtaler, 'ARBEIDSTRENING'), - MIDLERTIDIG_LONNSTILSKUDD: antallAvtaler(avtaler, 'MIDLERTIDIG_LONNSTILSKUDD'), - VARIG_LONNSTILSKUDD: antallAvtaler(avtaler, 'VARIG_LONNSTILSKUDD'), - SOMMERJOBB: antallAvtaler(avtaler, 'SOMMERJOBB'), - INKLUDERINGSTILSKUDD: antallAvtaler(avtaler, 'INKLUDERINGSTILSKUDD'), - MENTOR: antallAvtaler(avtaler, 'MENTOR'), - }), - [avtaler] - ); -}; - -const antallAvtaler = (avtaler: Arbeidsavtale[], tiltaktype: Avtalenavn) => - count(avtaler, (it) => it.tiltakstype === tiltaktype); - -interface Arbeidsavtale { - tiltakstype: string; -} - -const arbeidsavtaleResponsType = z.array( - z.object({ - tiltakstype: z.string(), - }) -); - -const fetcher = async (url: string) => { - try { - const respons = await fetch(url); - if (respons.ok) { - return arbeidsavtaleResponsType.parse(await respons.json()); - } - } catch (error) { - Sentry.captureException(error); - } - return arbeidsavtaleResponsType.parse([]); -}; diff --git a/src/api/arbeidsavtalerApi.ts b/src/api/arbeidsavtalerApi.ts new file mode 100644 index 000000000..1a4fee3a4 --- /dev/null +++ b/src/api/arbeidsavtalerApi.ts @@ -0,0 +1,13 @@ +export interface Arbeidsavtale { + tiltakstype: string; +} + +export async function hentArbeidsavtaler( + orgnr: string, +): Promise> { + const respons = await fetch(`/min-side-arbeidsgiver/tiltaksgjennomforing-api/avtaler/min-side-arbeidsgiver?bedriftNr=${orgnr}`); + if (respons.ok) { + return respons.json(); + } + return []; +} \ No newline at end of file diff --git a/src/api/arbeidsforholdApi.ts b/src/api/arbeidsforholdApi.ts new file mode 100644 index 000000000..dc8d162aa --- /dev/null +++ b/src/api/arbeidsforholdApi.ts @@ -0,0 +1,35 @@ +import { z } from 'zod'; +import * as Sentry from '@sentry/browser'; +import { Severity } from '@sentry/react'; + +const Oversikt = z.object({ + second: z.number().optional(), +}); + +export async function hentAntallArbeidsforholdFraAareg( + underenhet: string, + enhet: string +): Promise { + const respons = await fetch('/min-side-arbeidsgiver/antall-arbeidsforhold', { + headers: { + jurenhet: enhet, + orgnr: underenhet, + }, + }); + + if (!respons.ok) { + Sentry.captureMessage( + `hent antall arbeidsforhold fra aareg feilet med ${respons.status}`, + Severity.Warning + ); + return -1; + } + + try { + const { second = 0 } = Oversikt.parse(await respons.json()); + return second === 0 ? -1 : second; + } catch (error) { + Sentry.captureException(error); + return -1; + } +} diff --git a/src/api/presenterteKandidaterApi.ts b/src/api/presenterteKandidaterApi.ts new file mode 100644 index 000000000..2bc614205 --- /dev/null +++ b/src/api/presenterteKandidaterApi.ts @@ -0,0 +1,29 @@ +import { z } from 'zod'; +import * as Sentry from "@sentry/browser"; +import {Severity} from "@sentry/react"; + +const PresenterteKandidater = z.object({ + antallKandidater: z.number(), +}) + +export async function hentAntallKandidater( + orgnr: string, +): Promise { + const respons = await fetch( + `/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern/antallkandidater?virksomhetsnummer=${orgnr}`, + ); + + if (!respons.ok) { + Sentry.captureMessage(`hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}`, Severity.Warning) + return 0 + } + + try { + const {antallKandidater = 0} = PresenterteKandidater.parse(await respons.json()) + return antallKandidater + } catch (error) { + Sentry.captureException(error) + return 0 + } +} + diff --git "a/src/api/sykefrav\303\246rStatistikkApi.ts" "b/src/api/sykefrav\303\246rStatistikkApi.ts" new file mode 100644 index 000000000..b16c78e2c --- /dev/null +++ "b/src/api/sykefrav\303\246rStatistikkApi.ts" @@ -0,0 +1,27 @@ +import {z} from "zod"; +import * as Sentry from "@sentry/browser"; + +const Sykefraværsrespons = z.object({ + type: z.string(), + label: z.string(), + prosent: z.number(), +}); +export type Sykefraværsrespons = z.infer | undefined; + +export async function hentSykefravær( + orgnr: string, +): Promise { + const url = `/min-side-arbeidsgiver/api/sykefravaerstatistikk/${orgnr}`; + const respons = await fetch(url); + if (respons.ok) { + try { + return respons.status === 204 ? undefined : Sykefraværsrespons.parse(await respons.json()); + } catch (error) { + Sentry.captureException(error) + return undefined + } + } + throw new Error(`Kall til '${url}' feilet med ${respons.status}:${respons.statusText}`); +} + + diff --git a/src/index.tsx b/src/index.tsx index 41babad43..ad61013a5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,7 +23,7 @@ class SentryDebugTransport implements SentryTypes.Transport { } sendEvent(event: SentryTypes.Event): PromiseLike { - console.error('would have sent to sentry', event); + console.error('would have sent to sentry', JSON.stringify(event, null, 2)); return Promise.resolve({ status: 'success' }); } } From 149226e1ade5f641efd3a28bc173a08cc99cea7c Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Thu, 31 Aug 2023 18:29:20 +0200 Subject: [PATCH 10/25] disable etag og sett maxAge 1h --- server/server.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 74cedf252..069a569ca 100644 --- a/server/server.js +++ b/server/server.js @@ -255,7 +255,14 @@ const main = async () => { }); } - app.use('/min-side-arbeidsgiver/', express.static(BUILD_PATH, { index: false })); + app.use( + '/min-side-arbeidsgiver/', + express.static(BUILD_PATH, { + index: false, + etag: false, + maxAge: '1h', + }) + ); app.get('/min-side-arbeidsgiver/internal/isAlive', (req, res) => res.sendStatus(200)); app.get('/min-side-arbeidsgiver/internal/isReady', (req, res) => res.sendStatus(200)); From 21caec46e8d2a49756d405ecf4af12a2738215d3 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 09:24:09 +0200 Subject: [PATCH 11/25] oppdater codeql versjoner --- .github/workflows/codeql-analysis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 542a3c21e..256c6ea87 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From 270d149cbf2d30113046291a94264275328c9ef8 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 09:34:32 +0200 Subject: [PATCH 12/25] legg til permissions --- .github/workflows/codeql-analysis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 256c6ea87..7ad73b9e6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,6 +22,11 @@ on: jobs: analyze: + permissions: + actions: read + contents: read + security-events: write + name: Analyze runs-on: ubuntu-latest From 63c2bad02a72be568fb2b8b6e505580d874746e1 Mon Sep 17 00:00:00 2001 From: Peter Brottveit Bock Date: Fri, 1 Sep 2023 09:49:30 +0200 Subject: [PATCH 13/25] Revert "Revert "Swr og zod for tjenestebokser"" --- package-lock.json | 46 ++++ package.json | 1 + src/App/App.tsx | 257 ++++++++++-------- .../Arbeidsforholdboks/Arbeidsforholdboks.tsx | 24 +- .../useAntallArbeidsforholdFraAareg.ts | 61 +++++ .../ForebyggeFrav\303\246rboks.tsx" | 65 ++--- .../useSykefrav\303\246r.ts" | 36 +++ .../Kandidatlisteboks/Kandidatlisteboks.tsx | 45 ++- .../Kandidatlisteboks/useAntallKandidater.ts | 40 +++ .../Tiltakboks/Tiltakboks.tsx | 122 ++++----- .../Tiltakboks/useAvtaleoversikt.ts | 64 +++++ src/api/arbeidsavtalerApi.ts | 13 - src/api/arbeidsforholdApi.ts | 35 --- src/api/presenterteKandidaterApi.ts | 29 -- "src/api/sykefrav\303\246rStatistikkApi.ts" | 27 -- src/index.tsx | 2 +- 16 files changed, 496 insertions(+), 371 deletions(-) create mode 100644 src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts create mode 100644 "src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" create mode 100644 src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts create mode 100644 src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts delete mode 100644 src/api/arbeidsavtalerApi.ts delete mode 100644 src/api/arbeidsforholdApi.ts delete mode 100644 src/api/presenterteKandidaterApi.ts delete mode 100644 "src/api/sykefrav\303\246rStatistikkApi.ts" diff --git a/package-lock.json b/package-lock.json index a2ffdeaa3..128a1f32c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "react-tooltip": "^4.2.13", "regenerator-runtime": "^0.13.7", "smoothscroll-polyfill": "^0.4.4", + "swr": "^2.2.2", "typescript": "4.5.4", "uuid": "^9.0.0", "whatwg-fetch": "^3.6.2", @@ -7288,6 +7289,11 @@ "node": ">= 10" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -18602,6 +18608,18 @@ "tslib": "^2.0.3" } }, + "node_modules/swr": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.2.tgz", + "integrity": "sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -19327,6 +19345,14 @@ "braces": "^3.0.2" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -25701,6 +25727,11 @@ "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, + "client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -33882,6 +33913,15 @@ "tslib": "^2.0.3" } }, + "swr": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.2.tgz", + "integrity": "sha512-CbR41AoMD4TQBQw9ic3GTXspgfM9Y8Mdhb5Ob4uIKXhWqnRLItwA5fpGvB7SmSw3+zEjb0PdhiEumtUvYoQ+bQ==", + "requires": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + } + }, "symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -34402,6 +34442,12 @@ "braces": "^3.0.2" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index b9d95f091..06df93f10 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-tooltip": "^4.2.13", "regenerator-runtime": "^0.13.7", "smoothscroll-polyfill": "^0.4.4", + "swr": "^2.2.2", "typescript": "4.5.4", "uuid": "^9.0.0", "whatwg-fetch": "^3.6.2", diff --git a/src/App/App.tsx b/src/App/App.tsx index 048fe603b..d7416b114 100644 --- a/src/App/App.tsx +++ b/src/App/App.tsx @@ -18,6 +18,7 @@ import { SaksoversiktRestoreSession } from './Hovedside/Sak/Saksoversikt/Saksove import { Alert, Link } from '@navikt/ds-react'; import { gittMiljo } from '../utils/environment'; import Brodsmulesti from './Brodsmulesti/Brodsmulesti'; +import { SWRConfig } from 'swr'; const miljø = gittMiljo<'local' | 'labs' | 'dev' | 'prod'>({ prod: 'prod', @@ -56,127 +57,145 @@ const App: FunctionComponent = () => { return (
- - - - - - - - - - - - - - - } - /> - - - - } - /> - - - - } - /> - - - - - } - /> - - - - } - /> - - {' '} - Finner ikke siden.{' '} - + + + + + + + + + + + + + + + } + /> + + + + } + /> + + + + } + /> + + + + + } + /> + + + + } + /> + {' '} - Gå til Min side - arbeidsgiver - {' '} - - } - /> - - - - - - } - /> - - - - - + Finner ikke siden.{' '} + + {' '} + Gå til Min side + arbeidsgiver + {' '} + + } + /> + + + + + + } + /> + + + + + +
); }; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx index f81c0f01d..1fe78054a 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/Arbeidsforholdboks.tsx @@ -1,25 +1,13 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React from 'react'; import { innsynAaregURL } from '../../../../lenker'; import arbeidsforholdikon from './arbeidsforholdikon.svg'; -import { hentAntallArbeidsforholdFraAareg } from '../../../../api/arbeidsforholdApi'; -import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import { useAntallArbeidsforholdFraAareg } from './useAntallArbeidsforholdFraAareg'; import './ArbeidsforholdBoks.css'; import { Tjenesteboks } from '../Tjenesteboks'; const Arbeidsforholdboks = () => { - const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - const [antallArbeidsforhold, setAntallArbeidsforhold] = useState('–'); - useEffect(() => { - if (valgtOrganisasjon) - hentAntallArbeidsforholdFraAareg( - valgtOrganisasjon.organisasjon.OrganizationNumber, - valgtOrganisasjon.organisasjon.ParentOrganizationNumber ?? '' - ).then((antallArbeidsforholdRespons) => - setAntallArbeidsforhold( - antallArbeidsforholdRespons > 0 ? antallArbeidsforholdRespons.toString() : '–' - ) - ); - }, [valgtOrganisasjon]); + const antallArbeidsforhold = useAntallArbeidsforholdFraAareg(); + const orgnummerFraUrl = new URLSearchParams(window.location.search).get('bedrift') ?? ''; const href = innsynAaregURL + (orgnummerFraUrl === '' ? '' : `?bedrift=${orgnummerFraUrl}`); @@ -33,7 +21,9 @@ const Arbeidsforholdboks = () => {
{' '} - {antallArbeidsforhold} + + {antallArbeidsforhold > 0 ? antallArbeidsforhold : '-'} + arbeidsforhold (aktive og avsluttede){' '}
diff --git a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts new file mode 100644 index 000000000..b70f66866 --- /dev/null +++ b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts @@ -0,0 +1,61 @@ +import { z } from 'zod'; +import useSWR from 'swr'; +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import * as Sentry from '@sentry/browser'; +import { Severity } from '@sentry/react'; + +export const useAntallArbeidsforholdFraAareg = (): number => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + const { data } = useSWR( + valgtOrganisasjon !== undefined + ? { + url: '/min-side-arbeidsgiver/antall-arbeidsforhold', + jurenhet: valgtOrganisasjon.organisasjon.ParentOrganizationNumber ?? '', + orgnr: valgtOrganisasjon.organisasjon.OrganizationNumber, + } + : null, + fetcher + ); + + return data?.second ?? 0; +}; + +const Oversikt = z.object({ + second: z.number().optional(), +}); + +type AntallArbeidsforholdType = z.infer; + +const fetcher = async ({ + url, + jurenhet, + orgnr, +}: { + url: string; + jurenhet: string; + orgnr: string; +}) => { + const standardRespons: AntallArbeidsforholdType = { second: -1 }; + try { + const respons = await fetch(url, { + headers: { + jurenhet, + orgnr, + }, + }); + if (!respons.ok) { + Sentry.captureMessage( + `hent antall arbeidsforhold fra aareg feilet med ${respons.status}`, + Severity.Warning + ); + return standardRespons; + } + + const oversikt = Oversikt.parse(await respons.json()); + return oversikt.second === 0 ? standardRespons : oversikt; + } catch (error) { + Sentry.captureException(error); + return standardRespons; + } +}; diff --git "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" index a48757609..6c415c958 100644 --- "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" +++ "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/ForebyggeFrav\303\246rboks.tsx" @@ -1,12 +1,9 @@ -import {lenkeTilForebyggefravar} from '../../../../lenker'; -import React, {useContext, useEffect, useState} from 'react'; -import * as Sentry from "@sentry/browser"; +import { lenkeTilForebyggefravar } from '../../../../lenker'; +import React from 'react'; import ForebyggeFraværIkon from './ForebyggeFraværIkon.svg'; import './ForebyggeFraværboks.css'; -import {OrganisasjonsDetaljerContext} from '../../../OrganisasjonDetaljerProvider'; -import {hentSykefravær, Sykefraværsrespons} from '../../../../api/sykefraværStatistikkApi'; -import {StortTall, Tjenesteboks} from "../Tjenesteboks"; - +import { useSykefravær } from './useSykefravær'; +import { StortTall, Tjenesteboks } from '../Tjenesteboks'; const ForebyggeFraværboks = () => { const valgtbedrift = () => { @@ -14,52 +11,46 @@ const ForebyggeFraværboks = () => { return orgnummerFraUrl === '' ? '' : `?bedrift=${orgnummerFraUrl}`; }; - return - - ; + return ( + + + + ); }; -const beskrivelse = 'Verktøy for å forebygge fravær i din virksomhet.' +const beskrivelse = 'Verktøy for å forebygge fravær i din virksomhet.'; const Beskrivelse = () => { - const {valgtOrganisasjon} = useContext(OrganisasjonsDetaljerContext); - const [sykefravær, setSykefravær] = useState(undefined); - const statistikktype = (type: string) => { switch (type) { case 'NÆRING': case 'BRANSJE': - return 'bransje' - default : - return 'bedrift' + return 'bransje'; + default: + return 'bedrift'; } - } - useEffect(() => { - if (valgtOrganisasjon) - hentSykefravær(valgtOrganisasjon.organisasjon.OrganizationNumber).then(sykefraværsrespons => - setSykefravær(sykefraværsrespons), - ).catch(error => { - Sentry.captureException(error) - setSykefravær(undefined); - }); - }, [valgtOrganisasjon]); + }; + + const sykefravær = useSykefravær(); if (sykefravær !== undefined) { return ( - - {sykefravær.prosent.toString()} % - - <> legemeldt sykefravær i din {statistikktype(sykefravær.type)}. Lag en plan for å redusere fraværet. + {sykefravær.prosent.toString()} % + <> + {' '} + legemeldt sykefravær i din {statistikktype(sykefravær.type)}. Lag en plan for å + redusere fraværet.{' '} + ); } return {beskrivelse}; -} +}; export default ForebyggeFraværboks; diff --git "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" new file mode 100644 index 000000000..13a3cb7eb --- /dev/null +++ "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" @@ -0,0 +1,36 @@ +import { z } from 'zod'; +import useSWR from 'swr'; +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import * as Sentry from '@sentry/browser'; + +const Sykefraværsrespons = z.object({ + type: z.string(), + label: z.string(), + prosent: z.number(), +}); + +export type Sykefraværsrespons = z.infer; + +export const useSykefravær = (): Sykefraværsrespons | undefined => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + + const { data } = useSWR( + valgtOrganisasjon !== undefined + ? `/min-side-arbeidsgiver/api/sykefravaerstatistikk/${valgtOrganisasjon.organisasjon.OrganizationNumber}` + : null, + fetcher + ); + + return data; +}; + +const fetcher = async (url: string) => { + try { + const respons = await fetch(url); + return Sykefraværsrespons.parse(await respons.json()); + } catch (error) { + Sentry.captureException(error); + return undefined; + } +}; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx index 3c0cbf1e7..f411b5e44 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/Kandidatlisteboks.tsx @@ -1,36 +1,35 @@ -import React, {useContext, useEffect, useState} from 'react'; -import {OrganisasjonsDetaljerContext} from '../../../OrganisasjonDetaljerProvider'; -import {kandidatlisteURL} from '../../../../lenker'; -import {Tjenesteboks} from "../Tjenesteboks"; -import {hentAntallKandidater} from "../../../../api/presenterteKandidaterApi"; -import ikon from "./kandidatlisteboks-ikon.svg"; +import React from 'react'; +import { kandidatlisteURL } from '../../../../lenker'; +import { Tjenesteboks } from '../Tjenesteboks'; +import { useAntallKandidater } from './useAntallKandidater'; +import ikon from './kandidatlisteboks-ikon.svg'; import './Kandidatlisteboks.css'; const Kandidatlisteboks = () => { - const {valgtOrganisasjon} = useContext(OrganisasjonsDetaljerContext); - const [antallKandidater, setantallKandidater] = useState(0); - useEffect(() => { - if (valgtOrganisasjon) - hentAntallKandidater(valgtOrganisasjon.organisasjon.OrganizationNumber).then(antallKandidater => - setantallKandidater(antallKandidater) - ); - }, [valgtOrganisasjon]); + const antallKandidater = useAntallKandidater(); + const orgnummerFraUrl = new URLSearchParams(window.location.search).get('bedrift') ?? ''; - const href = kandidatlisteURL + (orgnummerFraUrl === '' ? '' : `?virksomhet=${orgnummerFraUrl}`); + const href = + kandidatlisteURL + (orgnummerFraUrl === '' ? '' : `?virksomhet=${orgnummerFraUrl}`); - return antallKandidater === 0 - ? null - : -
- {antallKandidater}kandidater -
+
+ + {' '} + + {antallKandidater} + kandidater{' '} + +
+ ); }; export default Kandidatlisteboks; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts new file mode 100644 index 000000000..a1e798208 --- /dev/null +++ b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts @@ -0,0 +1,40 @@ +import { z } from 'zod'; +import { useContext } from 'react'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import useSWR from 'swr'; +import * as Sentry from '@sentry/browser'; +import { Severity } from '@sentry/react'; + +export const useAntallKandidater = (): number => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + + const { data } = useSWR( + valgtOrganisasjon !== undefined + ? `/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern/antallkandidater?virksomhetsnummer=${valgtOrganisasjon.organisasjon.OrganizationNumber}` + : null, + fetcher + ); + + return data?.antallKandidater ?? 0; +}; + +const PresenterteKandidater = z.object({ + antallKandidater: z.number(), +}); + +const fetcher = async (url: string) => { + try { + const respons = await fetch(url); + if (!respons.ok) { + Sentry.captureMessage( + `hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}`, + Severity.Warning + ); + return { antallKandidater: 0 }; + } + return PresenterteKandidater.parse(await respons.json()); + } catch (error) { + Sentry.captureException(error); + return { antallKandidater: 0 }; + } +}; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx index 879275590..55b1d69bc 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx +++ b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/Tiltakboks.tsx @@ -1,98 +1,80 @@ -import React, {useContext, useEffect, useState} from 'react'; -import {tiltaksgjennomforingURL} from '../../../../lenker'; -import {OrganisasjonsDetaljerContext} from '../../../OrganisasjonDetaljerProvider'; +import React, { useContext } from 'react'; +import { tiltaksgjennomforingURL } from '../../../../lenker'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; import './Tiltakboks.css'; import tiltakikon from './tiltakboks-ikon.svg'; -import {Arbeidsavtale, hentArbeidsavtaler} from '../../../../api/arbeidsavtalerApi'; -import {Tjenesteboks} from "../Tjenesteboks"; -import {BodyShort} from "@navikt/ds-react"; -import * as Record from '../../../../utils/Record' +import { Avtalenavn, useAvtaleoversikt } from './useAvtaleoversikt'; +import { Tjenesteboks } from '../Tjenesteboks'; +import { BodyShort } from '@navikt/ds-react'; -const displayname = { - 'ARBEIDSTRENING': 'arbeidstrening', - 'MIDLERTIDIG_LONNSTILSKUDD': 'lønnstilskudd', - 'VARIG_LONNSTILSKUDD': 'varig lønnstilskudd', - 'SOMMERJOBB': 'sommerjobb', - 'INKLUDERINGSTILSKUDD': 'inkluderingstilskudd', - 'MENTOR': 'mentortilskudd', -} +const displayname: Record = { + ARBEIDSTRENING: 'arbeidstrening', + MIDLERTIDIG_LONNSTILSKUDD: 'lønnstilskudd', + VARIG_LONNSTILSKUDD: 'varig lønnstilskudd', + SOMMERJOBB: 'sommerjobb', + INKLUDERINGSTILSKUDD: 'inkluderingstilskudd', + MENTOR: 'mentortilskudd', +}; -const displayorder: (keyof typeof displayname)[] = [ +const displayorder: Avtalenavn[] = [ 'ARBEIDSTRENING', 'MIDLERTIDIG_LONNSTILSKUDD', 'VARIG_LONNSTILSKUDD', 'SOMMERJOBB', 'INKLUDERINGSTILSKUDD', 'MENTOR', -] +]; const Tiltakboks = () => { const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); - const orgnr = valgtOrganisasjon?.organisasjon?.OrganizationNumber - const [avtaleoversikt, setAvtaleoversikt] = useState>({}) + const orgnr = valgtOrganisasjon?.organisasjon?.OrganizationNumber; - useEffect(() => { - if (orgnr !== undefined) { - hentArbeidsavtaler(orgnr) - .then((avtaler: Arbeidsavtale[]) => { - const avtalerMedTiltaktype = (tiltaktype: string) => - avtaler.filter((avtale: Arbeidsavtale) => avtale.tiltakstype === tiltaktype).length; - setAvtaleoversikt({ - 'ARBEIDSTRENING': avtalerMedTiltaktype('ARBEIDSTRENING'), - 'MIDLERTIDIG_LONNSTILSKUDD': avtalerMedTiltaktype('MIDLERTIDIG_LONNSTILSKUDD'), - 'VARIG_LONNSTILSKUDD': avtalerMedTiltaktype('VARIG_LONNSTILSKUDD'), - 'SOMMERJOBB': avtalerMedTiltaktype('SOMMERJOBB'), - 'INKLUDERINGSTILSKUDD': avtalerMedTiltaktype('INKLUDERINGSTILSKUDD'), - 'MENTOR': avtalerMedTiltaktype('MENTOR'), - }) - }) - .catch(_ => { - setAvtaleoversikt({}) - }); - } - }, [orgnr]); + const avtaler = useAvtaleoversikt(); - const tiltakUrl = orgnr !== undefined && orgnr !== '' - ? `${tiltaksgjennomforingURL}&bedrift=${orgnr}` - : tiltaksgjennomforingURL; + const tiltakUrl = `${tiltaksgjennomforingURL}&bedrift=${orgnr ?? ''}`; - const tallElems = displayorder.flatMap(avtaletype => { - const antall = avtaleoversikt[avtaletype] ?? 0; + const tallElems = displayorder.flatMap((avtaletype) => { + const antall = avtaler[avtaletype]; return antall > 0 ? [ -
{antall}
, -
{displayname[avtaletype]}
, - ] +
+ {antall} +
, +
+ {displayname[avtaletype]} +
, + ] : []; - }) + }); - return -
- {tallElems.length > 0 - ?
- { tallElems } -
- : + return ( + - ; + > +
+ {tallElems.length > 0 ? ( +
{tallElems}
+ ) : ( + + )} +
+ + ); }; -const TekstUtenTall = () => +const TekstUtenTall = () => ( <> - + Arbeidstrening, lønnstilskudd, mentortilskudd, inkluderingstilskudd og sommerjobb. - - De ulike tiltakene krever egne tilganger i Altinn - - ; - + De ulike tiltakene krever egne tilganger i Altinn + +); export default Tiltakboks; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts new file mode 100644 index 000000000..70d092c53 --- /dev/null +++ b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts @@ -0,0 +1,64 @@ +import { useContext, useMemo } from 'react'; +import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; +import useSWR from 'swr'; +import { z } from 'zod'; +import * as Sentry from '@sentry/browser'; +import { count } from '../../../../utils/util'; + +export type Avtaleoversikt = { + ARBEIDSTRENING: number; + MIDLERTIDIG_LONNSTILSKUDD: number; + VARIG_LONNSTILSKUDD: number; + SOMMERJOBB: number; + INKLUDERINGSTILSKUDD: number; + MENTOR: number; +}; + +export type Avtalenavn = keyof Avtaleoversikt; + +export const useAvtaleoversikt = (): Avtaleoversikt => { + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + const { data: avtaler = [] } = useSWR( + valgtOrganisasjon !== undefined + ? `/min-side-arbeidsgiver/tiltaksgjennomforing-api/avtaler/min-side-arbeidsgiver?bedriftNr=${valgtOrganisasjon.organisasjon.OrganizationNumber}` + : null, + fetcher + ); + + return useMemo( + () => ({ + ARBEIDSTRENING: antallAvtaler(avtaler, 'ARBEIDSTRENING'), + MIDLERTIDIG_LONNSTILSKUDD: antallAvtaler(avtaler, 'MIDLERTIDIG_LONNSTILSKUDD'), + VARIG_LONNSTILSKUDD: antallAvtaler(avtaler, 'VARIG_LONNSTILSKUDD'), + SOMMERJOBB: antallAvtaler(avtaler, 'SOMMERJOBB'), + INKLUDERINGSTILSKUDD: antallAvtaler(avtaler, 'INKLUDERINGSTILSKUDD'), + MENTOR: antallAvtaler(avtaler, 'MENTOR'), + }), + [avtaler] + ); +}; + +const antallAvtaler = (avtaler: Arbeidsavtale[], tiltaktype: Avtalenavn) => + count(avtaler, (it) => it.tiltakstype === tiltaktype); + +interface Arbeidsavtale { + tiltakstype: string; +} + +const arbeidsavtaleResponsType = z.array( + z.object({ + tiltakstype: z.string(), + }) +); + +const fetcher = async (url: string) => { + try { + const respons = await fetch(url); + if (respons.ok) { + return arbeidsavtaleResponsType.parse(await respons.json()); + } + } catch (error) { + Sentry.captureException(error); + } + return arbeidsavtaleResponsType.parse([]); +}; diff --git a/src/api/arbeidsavtalerApi.ts b/src/api/arbeidsavtalerApi.ts deleted file mode 100644 index 1a4fee3a4..000000000 --- a/src/api/arbeidsavtalerApi.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface Arbeidsavtale { - tiltakstype: string; -} - -export async function hentArbeidsavtaler( - orgnr: string, -): Promise> { - const respons = await fetch(`/min-side-arbeidsgiver/tiltaksgjennomforing-api/avtaler/min-side-arbeidsgiver?bedriftNr=${orgnr}`); - if (respons.ok) { - return respons.json(); - } - return []; -} \ No newline at end of file diff --git a/src/api/arbeidsforholdApi.ts b/src/api/arbeidsforholdApi.ts deleted file mode 100644 index dc8d162aa..000000000 --- a/src/api/arbeidsforholdApi.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { z } from 'zod'; -import * as Sentry from '@sentry/browser'; -import { Severity } from '@sentry/react'; - -const Oversikt = z.object({ - second: z.number().optional(), -}); - -export async function hentAntallArbeidsforholdFraAareg( - underenhet: string, - enhet: string -): Promise { - const respons = await fetch('/min-side-arbeidsgiver/antall-arbeidsforhold', { - headers: { - jurenhet: enhet, - orgnr: underenhet, - }, - }); - - if (!respons.ok) { - Sentry.captureMessage( - `hent antall arbeidsforhold fra aareg feilet med ${respons.status}`, - Severity.Warning - ); - return -1; - } - - try { - const { second = 0 } = Oversikt.parse(await respons.json()); - return second === 0 ? -1 : second; - } catch (error) { - Sentry.captureException(error); - return -1; - } -} diff --git a/src/api/presenterteKandidaterApi.ts b/src/api/presenterteKandidaterApi.ts deleted file mode 100644 index 2bc614205..000000000 --- a/src/api/presenterteKandidaterApi.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from 'zod'; -import * as Sentry from "@sentry/browser"; -import {Severity} from "@sentry/react"; - -const PresenterteKandidater = z.object({ - antallKandidater: z.number(), -}) - -export async function hentAntallKandidater( - orgnr: string, -): Promise { - const respons = await fetch( - `/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern/antallkandidater?virksomhetsnummer=${orgnr}`, - ); - - if (!respons.ok) { - Sentry.captureMessage(`hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}`, Severity.Warning) - return 0 - } - - try { - const {antallKandidater = 0} = PresenterteKandidater.parse(await respons.json()) - return antallKandidater - } catch (error) { - Sentry.captureException(error) - return 0 - } -} - diff --git "a/src/api/sykefrav\303\246rStatistikkApi.ts" "b/src/api/sykefrav\303\246rStatistikkApi.ts" deleted file mode 100644 index b16c78e2c..000000000 --- "a/src/api/sykefrav\303\246rStatistikkApi.ts" +++ /dev/null @@ -1,27 +0,0 @@ -import {z} from "zod"; -import * as Sentry from "@sentry/browser"; - -const Sykefraværsrespons = z.object({ - type: z.string(), - label: z.string(), - prosent: z.number(), -}); -export type Sykefraværsrespons = z.infer | undefined; - -export async function hentSykefravær( - orgnr: string, -): Promise { - const url = `/min-side-arbeidsgiver/api/sykefravaerstatistikk/${orgnr}`; - const respons = await fetch(url); - if (respons.ok) { - try { - return respons.status === 204 ? undefined : Sykefraværsrespons.parse(await respons.json()); - } catch (error) { - Sentry.captureException(error) - return undefined - } - } - throw new Error(`Kall til '${url}' feilet med ${respons.status}:${respons.statusText}`); -} - - diff --git a/src/index.tsx b/src/index.tsx index ad61013a5..41babad43 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -23,7 +23,7 @@ class SentryDebugTransport implements SentryTypes.Transport { } sendEvent(event: SentryTypes.Event): PromiseLike { - console.error('would have sent to sentry', JSON.stringify(event, null, 2)); + console.error('would have sent to sentry', event); return Promise.resolve({ status: 'success' }); } } From 73aa08f2355e99184aba30a2c3ec5110011c53a1 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 09:52:03 +0200 Subject: [PATCH 14/25] handle sigterm with graceful shutdown --- server/server.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 069a569ca..fb4766e8d 100644 --- a/server/server.js +++ b/server/server.js @@ -293,9 +293,16 @@ const main = async () => { }, 60 * 1000); } - app.listen(PORT, () => { + const server = app.listen(PORT, () => { log.info(`Server listening on port ${PORT}`); }); + + process.on('SIGTERM', () => { + log.info('SIGTERM signal received: closing HTTP server'); + server.close(() => { + log.info('HTTP server closed'); + }); + }); }; main() From 2390930b953aaadde6ad2ad55124ad95351fbbb7 Mon Sep 17 00:00:00 2001 From: ebelegu Date: Fri, 1 Sep 2023 10:50:07 +0200 Subject: [PATCH 15/25] =?UTF-8?q?h=C3=A5ndterer=20401=20og=20200=20eksplis?= =?UTF-8?q?itt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../useAntallArbeidsforholdFraAareg.ts | 23 +++++++------------ .../useSykefrav\303\246r.ts" | 8 ++++++- .../Kandidatlisteboks/useAntallKandidater.ts | 20 ++++++++-------- .../Tiltakboks/useAvtaleoversikt.ts | 13 +++++++---- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts index b70f66866..9c4256f20 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts +++ b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts @@ -3,7 +3,6 @@ import useSWR from 'swr'; import { useContext } from 'react'; import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; import * as Sentry from '@sentry/browser'; -import { Severity } from '@sentry/react'; export const useAntallArbeidsforholdFraAareg = (): number => { const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); @@ -18,15 +17,13 @@ export const useAntallArbeidsforholdFraAareg = (): number => { fetcher ); - return data?.second ?? 0; + return data ?? 0; }; const Oversikt = z.object({ second: z.number().optional(), }); -type AntallArbeidsforholdType = z.infer; - const fetcher = async ({ url, jurenhet, @@ -36,7 +33,6 @@ const fetcher = async ({ jurenhet: string; orgnr: string; }) => { - const standardRespons: AntallArbeidsforholdType = { second: -1 }; try { const respons = await fetch(url, { headers: { @@ -44,18 +40,15 @@ const fetcher = async ({ orgnr, }, }); - if (!respons.ok) { - Sentry.captureMessage( - `hent antall arbeidsforhold fra aareg feilet med ${respons.status}`, - Severity.Warning - ); - return standardRespons; - } + if (respons.status === 200) return Oversikt.parse(await respons.json()).second; + if (respons.status === 401) return 0; - const oversikt = Oversikt.parse(await respons.json()); - return oversikt.second === 0 ? standardRespons : oversikt; + Sentry.captureMessage( + `hent antall arbeidsforhold fra aareg feilet med ${respons.status}, ${respons.statusText}` + ); + return 0; } catch (error) { Sentry.captureException(error); - return standardRespons; + return 0; } }; diff --git "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" index 13a3cb7eb..26bcac321 100644 --- "a/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" +++ "b/src/App/Hovedside/TjenesteBoksContainer/ForebyggeFrav\303\246rboks/useSykefrav\303\246r.ts" @@ -28,7 +28,13 @@ export const useSykefravær = (): Sykefraværsrespons | undefined => { const fetcher = async (url: string) => { try { const respons = await fetch(url); - return Sykefraværsrespons.parse(await respons.json()); + if (respons.status === 204) return undefined; + if (respons.status === 200) return Sykefraværsrespons.parse(await respons.json()); + if (respons.status === 401) return undefined; + Sentry.captureMessage( + `hent sykefraværsstatistikk fra min-side-arbeidsgiver-api feilet med ${respons.status}, ${respons.statusText}` + ); + return undefined; } catch (error) { Sentry.captureException(error); return undefined; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts index a1e798208..112f0aeab 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts +++ b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts @@ -15,7 +15,7 @@ export const useAntallKandidater = (): number => { fetcher ); - return data?.antallKandidater ?? 0; + return data ?? 0; }; const PresenterteKandidater = z.object({ @@ -25,16 +25,16 @@ const PresenterteKandidater = z.object({ const fetcher = async (url: string) => { try { const respons = await fetch(url); - if (!respons.ok) { - Sentry.captureMessage( - `hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}`, - Severity.Warning - ); - return { antallKandidater: 0 }; - } - return PresenterteKandidater.parse(await respons.json()); + + if (respons.status === 200) + return PresenterteKandidater.parse(await respons.json()).antallKandidater; + if (respons.status === 401) return 0; + Sentry.captureMessage( + `hent antall kandidater fra presenterte-kandidater-api feilet med ${respons.status}, ${respons.statusText}` + ); + return 0; } catch (error) { Sentry.captureException(error); - return { antallKandidater: 0 }; + return 0; } }; diff --git a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts index 70d092c53..32b9bf325 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts +++ b/src/App/Hovedside/TjenesteBoksContainer/Tiltakboks/useAvtaleoversikt.ts @@ -45,7 +45,7 @@ interface Arbeidsavtale { tiltakstype: string; } -const arbeidsavtaleResponsType = z.array( +const ArbeidsavtaleResponsType = z.array( z.object({ tiltakstype: z.string(), }) @@ -54,11 +54,14 @@ const arbeidsavtaleResponsType = z.array( const fetcher = async (url: string) => { try { const respons = await fetch(url); - if (respons.ok) { - return arbeidsavtaleResponsType.parse(await respons.json()); - } + if (respons.status === 200) return ArbeidsavtaleResponsType.parse(await respons.json()); + if (respons.status === 401) return []; + Sentry.captureMessage( + `hent arbeidsavtaler fra tiltaksgjennomforing-api feilet med ${respons.status}, ${respons.statusText}` + ); + return []; } catch (error) { Sentry.captureException(error); } - return arbeidsavtaleResponsType.parse([]); + return []; }; From 4de875739d767cb30f05a9427dbf99e33537665f Mon Sep 17 00:00:00 2001 From: ebelegu Date: Fri, 1 Sep 2023 11:04:17 +0200 Subject: [PATCH 16/25] remove unused import --- .../Kandidatlisteboks/useAntallKandidater.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts index 112f0aeab..4d8e1eb63 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts +++ b/src/App/Hovedside/TjenesteBoksContainer/Kandidatlisteboks/useAntallKandidater.ts @@ -3,7 +3,6 @@ import { useContext } from 'react'; import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; import useSWR from 'swr'; import * as Sentry from '@sentry/browser'; -import { Severity } from '@sentry/react'; export const useAntallKandidater = (): number => { const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); From c3f9f498cc5722d70c89c41e364f69453f41dede Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 11:26:49 +0200 Subject: [PATCH 17/25] bump http-proxy-middleware til 3.0.0-beta.1 fro graceful shutdown av proxy --- server/package-lock.json | 106 ++++++++++++++++++++++++++------------- server/package.json | 2 +- server/server.js | 53 +++++++++++--------- 3 files changed, 102 insertions(+), 59 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index a0c08377a..f1fedd66c 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -15,7 +15,7 @@ "cookie-parser": "^1.4.6", "express": "^4.17.3", "express-http-proxy": "1.6.3", - "http-proxy-middleware": "^1.0.6", + "http-proxy-middleware": "3.0.0-beta.1", "jsdom": "^16.4.0", "mustache": "^4.2.0", "node-fetch": "^3.2.10", @@ -440,9 +440,9 @@ "integrity": "sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==" }, "node_modules/@types/http-proxy": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz", - "integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==", + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", "dependencies": { "@types/node": "*" } @@ -1540,20 +1540,42 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/http-proxy-middleware": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz", - "integrity": "sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg==", + "version": "3.0.0-beta.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0-beta.1.tgz", + "integrity": "sha512-hdiTlVVoaxncf239csnEpG5ew2lRWnoNR1PMWOO6kYulSphlrfLs5JFZtFVH3R5EUWSZNMkeUqvkvfctuWaK8A==", "dependencies": { - "@types/http-proxy": "^1.17.5", + "@types/http-proxy": "^1.17.10", + "debug": "^4.3.4", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "micromatch": "^4.0.5" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/http-proxy-middleware/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -1827,12 +1849,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" @@ -2037,9 +2059,9 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "engines": { "node": ">=8.6" }, @@ -2999,9 +3021,9 @@ "integrity": "sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q==" }, "@types/http-proxy": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.7.tgz", - "integrity": "sha512-9hdj6iXH64tHSLTY+Vt2eYOGzSogC+JQ2H7bdPWkuh7KXP5qLllWx++t+K9Wk556c3dkDdPws/SpMRi0sdCT1w==", + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", "requires": { "@types/node": "*" } @@ -3844,15 +3866,31 @@ } }, "http-proxy-middleware": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz", - "integrity": "sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg==", + "version": "3.0.0-beta.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0-beta.1.tgz", + "integrity": "sha512-hdiTlVVoaxncf239csnEpG5ew2lRWnoNR1PMWOO6kYulSphlrfLs5JFZtFVH3R5EUWSZNMkeUqvkvfctuWaK8A==", "requires": { - "@types/http-proxy": "^1.17.5", + "@types/http-proxy": "^1.17.10", + "debug": "^4.3.4", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "micromatch": "^4.0.5" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "https-proxy-agent": { @@ -4061,12 +4099,12 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "mime": { @@ -4203,9 +4241,9 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pkginfo": { "version": "0.4.1", diff --git a/server/package.json b/server/package.json index ae3f5a6a6..59041647f 100644 --- a/server/package.json +++ b/server/package.json @@ -11,7 +11,7 @@ "cookie-parser": "^1.4.6", "express": "^4.17.3", "express-http-proxy": "1.6.3", - "http-proxy-middleware": "^1.0.6", + "http-proxy-middleware": "3.0.0-beta.1", "jsdom": "^16.4.0", "mustache": "^4.2.0", "node-fetch": "^3.2.10", diff --git a/server/server.js b/server/server.js index fb4766e8d..d57498dae 100644 --- a/server/server.js +++ b/server/server.js @@ -119,11 +119,13 @@ const main = async () => { } else { const proxyOptions = { logLevel: PROXY_LOG_LEVEL, - logProvider: (_) => log, - onError: (err, req, res) => { - log.error( - `${req.method} ${req.path} => [${res.statusCode}:${res.statusText}]: ${err.message}` - ); + logger: log, + on: { + error: (err, req, res) => { + log.error( + `${req.method} ${req.path} => [${res.statusCode}:${res.statusText}]: ${err.message}` + ); + }, }, secure: true, xfwd: true, @@ -141,27 +143,30 @@ const main = async () => { createProxyMiddleware({ ...proxyOptions, selfHandleResponse: true, // res.end() will be called internally by responseInterceptor() - onProxyRes: responseInterceptor(async (responseBuffer, proxyRes) => { - try { - if (proxyRes.statusCode >= 400) { - log.warn( - `tiltaksgjennomforing-api/avtaler feilet ${proxyRes.statusCode}: ${proxyRes.statusMessage}` - ); + on: { + ...proxyOptions.on, + proxyRes: responseInterceptor(async (responseBuffer, proxyRes) => { + try { + if (proxyRes.statusCode >= 400) { + log.warn( + `tiltaksgjennomforing-api/avtaler feilet ${proxyRes.statusCode}: ${proxyRes.statusMessage}` + ); + return JSON.stringify([]); + } + if (proxyRes.headers['content-type'] === 'application/json') { + const data = JSON.parse(responseBuffer.toString('utf8')).map( + (elem) => ({ + tiltakstype: elem.tiltakstype, + }) + ); + return JSON.stringify(data); + } + } catch (error) { + log.error(`tiltaksgjennomforing-api/avtaler feilet ${error}`); return JSON.stringify([]); } - if (proxyRes.headers['content-type'] === 'application/json') { - const data = JSON.parse(responseBuffer.toString('utf8')).map( - (elem) => ({ - tiltakstype: elem.tiltakstype, - }) - ); - return JSON.stringify(data); - } - } catch (error) { - log.error(`tiltaksgjennomforing-api/avtaler feilet ${error}`); - return JSON.stringify([]); - } - }), + }), + }, pathRewrite: { '^/min-side-arbeidsgiver/': '/', }, From c9721ccf36315719bb085f86c9dc91944aae291d Mon Sep 17 00:00:00 2001 From: ebelegu Date: Fri, 1 Sep 2023 11:29:50 +0200 Subject: [PATCH 18/25] Default sortering satt til oppdatert --- .../Saksoversikt/useOversiktSessionStorage.ts | 250 ++++++++++-------- 1 file changed, 140 insertions(+), 110 deletions(-) diff --git a/src/App/Hovedside/Sak/Saksoversikt/useOversiktSessionStorage.ts b/src/App/Hovedside/Sak/Saksoversikt/useOversiktSessionStorage.ts index ea9812425..033d4fb0e 100644 --- a/src/App/Hovedside/Sak/Saksoversikt/useOversiktSessionStorage.ts +++ b/src/App/Hovedside/Sak/Saksoversikt/useOversiktSessionStorage.ts @@ -4,35 +4,38 @@ import { useContext, useEffect, useMemo, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { useSessionStorage } from '../../../hooks/useStorage'; -import {equalAsSets, Filter} from './useOversiktStateTransitions'; +import { equalAsSets, Filter } from './useOversiktStateTransitions'; import { OrganisasjonsDetaljerContext } from '../../../OrganisasjonDetaljerProvider'; -import {OppgaveTilstand, SakSortering} from "../../../../api/graphql-types"; +import { OppgaveTilstand, SakSortering } from '../../../../api/graphql-types'; import { Set } from 'immutable'; import { Organisasjon } from '../../../../altinn/organisasjon'; -const SESSION_STORAGE_KEY = 'saksoversiktfilter' +const SESSION_STORAGE_KEY = 'saksoversiktfilter'; type SessionStateSaksoversikt = { - route: "/saksoversikt", - side: number, - tekstsoek: string, - virksomhetsnumre: string[] | "ALLEBEDRIFTER", - sortering: SakSortering, - bedrift: string | undefined, - sakstyper: string[], - oppgaveTilstand: OppgaveTilstand[], - valgtFilterId: string | undefined, -} + route: '/saksoversikt'; + side: number; + tekstsoek: string; + virksomhetsnumre: string[] | 'ALLEBEDRIFTER'; + sortering: SakSortering; + bedrift: string | undefined; + sakstyper: string[]; + oppgaveTilstand: OppgaveTilstand[]; + valgtFilterId: string | undefined; +}; type SessionStateForside = { - route: "/", - bedrift: string | undefined, -} + route: '/'; + bedrift: string | undefined; +}; -type SessionState = SessionStateSaksoversikt | SessionStateForside +type SessionState = SessionStateSaksoversikt | SessionStateForside; -const filterToSessionState = (filter: Filter, valgtFilterId: string | undefined): SessionStateSaksoversikt => ({ +const filterToSessionState = ( + filter: Filter, + valgtFilterId: string | undefined +): SessionStateSaksoversikt => ({ route: '/saksoversikt', - bedrift: new URLSearchParams(window.location.search).get("bedrift") ?? undefined, + bedrift: new URLSearchParams(window.location.search).get('bedrift') ?? undefined, side: filter.side, tekstsoek: filter.tekstsoek, sortering: filter.sortering, @@ -40,18 +43,23 @@ const filterToSessionState = (filter: Filter, valgtFilterId: string | undefined) sakstyper: filter.sakstyper, oppgaveTilstand: filter.oppgaveTilstand, valgtFilterId, -}) +}); -const equalVirksomhetsnumre = (a: SessionStateSaksoversikt, b: SessionStateSaksoversikt): boolean => { +const equalVirksomhetsnumre = ( + a: SessionStateSaksoversikt, + b: SessionStateSaksoversikt +): boolean => { const virksomheterA = a.virksomhetsnumre; const virksomheterB = b.virksomhetsnumre; if (virksomheterA === 'ALLEBEDRIFTER' && virksomheterB === 'ALLEBEDRIFTER') { - - return true; } else if (Array.isArray(virksomheterA) && Array.isArray(virksomheterB)) { - return virksomheterA.length === virksomheterB.length && - virksomheterA.every(aVirksomhet => virksomheterB.some(bVirksomhet => aVirksomhet === bVirksomhet)); + return ( + virksomheterA.length === virksomheterB.length && + virksomheterA.every((aVirksomhet) => + virksomheterB.some((bVirksomhet) => aVirksomhet === bVirksomhet) + ) + ); } else { return false; } @@ -61,7 +69,8 @@ export const equalSessionState = (a: SessionState, b: SessionState): boolean => if (a.route === '/' && b.route === '/') { return a.bedrift === b.bedrift; } else if (a.route === '/saksoversikt' && b.route === '/saksoversikt') { - return a.side === b.side && + return ( + a.side === b.side && a.tekstsoek === b.tekstsoek && a.bedrift === b.bedrift && a.sortering === b.sortering && @@ -69,183 +78,204 @@ export const equalSessionState = (a: SessionState, b: SessionState): boolean => equalVirksomhetsnumre(a, b) && equalAsSets(a.sakstyper, b.sakstyper) && equalAsSets(a.oppgaveTilstand, b.oppgaveTilstand) + ); } else { return false; } -} +}; export const useSessionStateForside = (): void => { - const {valgtOrganisasjon} = useContext(OrganisasjonsDetaljerContext) - const bedrift = valgtOrganisasjon?.organisasjon?.OrganizationNumber + const { valgtOrganisasjon } = useContext(OrganisasjonsDetaljerContext); + const bedrift = valgtOrganisasjon?.organisasjon?.OrganizationNumber; useEffect(() => { const sessionState: SessionStateForside = { route: '/', bedrift, - } + }; sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(sessionState)); - }, [bedrift]) -} + }, [bedrift]); +}; export type UseSessionState = [ { - filter: Filter, - valgtFilterId: string | undefined, + filter: Filter; + valgtFilterId: string | undefined; }, - (filter: Filter, valgtFilterId: string | undefined) => void -] + (filter: Filter, valgtFilterId: string | undefined) => void, +]; export const useSessionState = (alleVirksomheter: Organisasjon[]): UseSessionState => { const [sessionState, setSessionState] = useState(() => extractSearchParameters(window.location.search) - ) + ); - const location = useLocation() - const navigate = useNavigate() + const location = useLocation(); + const navigate = useNavigate(); useEffect(() => { - const newSessionState = extractSearchParameters(location.search) + const newSessionState = extractSearchParameters(location.search); if (!equalSessionState(sessionState, newSessionState)) { - setSessionState(newSessionState) + setSessionState(newSessionState); } - }, [location.search]) + }, [location.search]); useEffect(() => { sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(sessionState)); - }, [sessionState]) + }, [sessionState]); const update = (newFilter: Filter, newValgtFilterId: string | undefined) => { - const newSessionState = filterToSessionState(newFilter, newValgtFilterId) + const newSessionState = filterToSessionState(newFilter, newValgtFilterId); if (!equalSessionState(sessionState, newSessionState)) { - const search = updateSearchParameters(location.search, newSessionState) + const search = updateSearchParameters(location.search, newSessionState); if (search !== location.search) { - navigate({search}, {replace: true}); + navigate({ search }, { replace: true }); } - setSessionState(newSessionState) + setSessionState(newSessionState); } - } + }; const filter = useMemo(() => { return { route: '/saksoversikt', side: sessionState.side, tekstsoek: sessionState.tekstsoek, - virksomheter: sessionState.virksomhetsnumre === "ALLEBEDRIFTER" - ? Set() - : Set(sessionState.virksomhetsnumre.flatMap(orgnr => { - const org = alleVirksomheter.find(org => org.OrganizationNumber === orgnr) - return org !== undefined ? [orgnr] : []; - })), + virksomheter: + sessionState.virksomhetsnumre === 'ALLEBEDRIFTER' + ? Set() + : Set( + sessionState.virksomhetsnumre.flatMap((orgnr) => { + const org = alleVirksomheter.find( + (org) => org.OrganizationNumber === orgnr + ); + return org !== undefined ? [orgnr] : []; + }) + ), sortering: sessionState.sortering, sakstyper: sessionState.sakstyper, oppgaveTilstand: sessionState.oppgaveTilstand, - } + }; }, [ sessionState.side, sessionState.tekstsoek, - sessionState.virksomhetsnumre === "ALLEBEDRIFTER" ? "ALLEBEDRIFTER" : sessionState.virksomhetsnumre.join(","), + sessionState.virksomhetsnumre === 'ALLEBEDRIFTER' + ? 'ALLEBEDRIFTER' + : sessionState.virksomhetsnumre.join(','), sessionState.sortering, - sessionState.sakstyper.join(","), - sessionState.oppgaveTilstand.join(","), - ]) + sessionState.sakstyper.join(','), + sessionState.oppgaveTilstand.join(','), + ]); - return [{filter, valgtFilterId: sessionState.valgtFilterId }, update] -} + return [{ filter, valgtFilterId: sessionState.valgtFilterId }, update]; +}; const extractSearchParameters = (searchString: string): SessionStateSaksoversikt => { - const search = new URLSearchParams(searchString) - const sortering = (search.get("sortering") ?? SakSortering.Frist) as SakSortering - const bedrift = search.get("bedrift") ?? undefined; - const virksomhetsnumre = search.get("virksomhetsnumre") === "ALLEBEDRIFTER" ? - "ALLEBEDRIFTER" - : search.get("virksomhetsnumre")?.split(",") ?? "ALLEBEDRIFTER"; + const search = new URLSearchParams(searchString); + const sortering = (search.get('sortering') ?? SakSortering.Oppdatert) as SakSortering; + const bedrift = search.get('bedrift') ?? undefined; + const virksomhetsnumre = + search.get('virksomhetsnumre') === 'ALLEBEDRIFTER' + ? 'ALLEBEDRIFTER' + : search.get('virksomhetsnumre')?.split(',') ?? 'ALLEBEDRIFTER'; return { route: '/saksoversikt', bedrift, virksomhetsnumre, - tekstsoek: search.get("tekstsoek") ?? '', - side: Number.parseInt(search.get("side") ?? '1'), + tekstsoek: search.get('tekstsoek') ?? '', + side: Number.parseInt(search.get('side') ?? '1'), sortering: Object.values(SakSortering).includes(sortering) ? sortering : SakSortering.Frist, - sakstyper: search.get("sakstyper")?.split(",") ?? [], - oppgaveTilstand: search.get("oppgaveTilstand")?.split(",") as OppgaveTilstand[] ?? [] , - valgtFilterId: search.get("valgtFilterId") ?? undefined, - } -} + sakstyper: search.get('sakstyper')?.split(',') ?? [], + oppgaveTilstand: (search.get('oppgaveTilstand')?.split(',') as OppgaveTilstand[]) ?? [], + valgtFilterId: search.get('valgtFilterId') ?? undefined, + }; +}; -const updateSearchParameters = (current: string, sessionState: SessionStateSaksoversikt): string => { - const query = new URLSearchParams(current) +const updateSearchParameters = ( + current: string, + sessionState: SessionStateSaksoversikt +): string => { + const query = new URLSearchParams(current); if (sessionState.tekstsoek.length > 0) { - query.set("tekstsoek", sessionState.tekstsoek) + query.set('tekstsoek', sessionState.tekstsoek); } else { - query.delete("tekstsoek") + query.delete('tekstsoek'); } - const side = sessionState.side + const side = sessionState.side; if (side === 1) { - query.delete("side") + query.delete('side'); } else { - query.set("side", sessionState.side.toString()) + query.set('side', sessionState.side.toString()); } if (sessionState.bedrift !== undefined) { - query.set("bedrift", sessionState.bedrift) + query.set('bedrift', sessionState.bedrift); } - if (sessionState.virksomhetsnumre === "ALLEBEDRIFTER") { - query.delete("virksomhetsnumre") + if (sessionState.virksomhetsnumre === 'ALLEBEDRIFTER') { + query.delete('virksomhetsnumre'); } else { - query.set("virksomhetsnumre", sessionState.virksomhetsnumre.join(",")); + query.set('virksomhetsnumre', sessionState.virksomhetsnumre.join(',')); } - - if (sessionState.sakstyper.length > 0){ - query.set("sakstyper", sessionState.sakstyper.join(",")) + + if (sessionState.sakstyper.length > 0) { + query.set('sakstyper', sessionState.sakstyper.join(',')); } else { - query.delete("sakstyper") + query.delete('sakstyper'); } if (sessionState.sortering === SakSortering.Frist) { - query.delete("sortering") + query.delete('sortering'); } else { - query.set("sortering", sessionState.sortering); + query.set('sortering', sessionState.sortering); } if (sessionState.oppgaveTilstand.length === 0) { - query.delete("oppgaveTilstand") + query.delete('oppgaveTilstand'); } else { - query.set("oppgaveTilstand", sessionState.oppgaveTilstand.toString()) + query.set('oppgaveTilstand', sessionState.oppgaveTilstand.toString()); } if (sessionState.valgtFilterId === undefined) { - query.delete("valgtFilterId") + query.delete('valgtFilterId'); } else { - query.set("valgtFilterId", sessionState.valgtFilterId) + query.set('valgtFilterId', sessionState.valgtFilterId); } - return query.toString() -} + return query.toString(); +}; // Clear sessionStorage with oversikts-filter. export const useOversiktsfilterClearing = () => { - const [, , deleteFromSession] = useSessionStorage(SESSION_STORAGE_KEY, undefined) + const [, , deleteFromSession] = useSessionStorage( + SESSION_STORAGE_KEY, + undefined + ); useEffect(() => { - deleteFromSession() - }, []) -} + deleteFromSession(); + }, []); +}; export const useRestoreSessionFromStorage = () => { - const [storedSession] = useSessionStorage(SESSION_STORAGE_KEY, undefined) - const location = useLocation() + const [storedSession] = useSessionStorage( + SESSION_STORAGE_KEY, + undefined + ); + const location = useLocation(); const navigate = useNavigate(); return () => { if (storedSession?.route === '/saksoversikt') { - const search = updateSearchParameters(location.search, storedSession) - navigate({pathname: "/saksoversikt", search}, {replace: true}) + const search = updateSearchParameters(location.search, storedSession); + navigate({ pathname: '/saksoversikt', search }, { replace: true }); } else if (storedSession?.route === '/') { - const search = storedSession.bedrift === undefined ? undefined : `bedrift=${storedSession.bedrift}` - navigate({pathname: "/", search}, {replace: true}) + const search = + storedSession.bedrift === undefined + ? undefined + : `bedrift=${storedSession.bedrift}`; + navigate({ pathname: '/', search }, { replace: true }); } else { - navigate({pathname: "/saksoversikt"}, {replace: true}) + navigate({ pathname: '/saksoversikt' }, { replace: true }); } - } -} + }; +}; From 4a37073ca5b0c6ffe16e5888faeaf813cdff6612 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 12:39:06 +0200 Subject: [PATCH 19/25] =?UTF-8?q?endre=20til=20=C3=A5=20fungere=20uten=20r?= =?UTF-8?q?eq.url=20patching=20som=20er=20fjernet=20i=203.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nais/dev-gcp.yaml | 2 - nais/labs-gcp.yaml | 2 - nais/prod-gcp.yaml | 2 - server/mock/antallArbeidsforholdMock.js | 17 +++--- server/server.js | 59 ++++--------------- .../useAntallArbeidsforholdFraAareg.ts | 2 +- 6 files changed, 23 insertions(+), 61 deletions(-) diff --git a/nais/dev-gcp.yaml b/nais/dev-gcp.yaml index 6b36b3d1f..db7390a09 100644 --- a/nais/dev-gcp.yaml +++ b/nais/dev-gcp.yaml @@ -32,8 +32,6 @@ spec: env: - name: NODE_EXTRA_CA_CERTS value: /etc/ssl/ca-bundle.pem - - name: BACKEND_API_URL - value: http://min-side-arbeidsgiver-api.fager - name: LOGIN_URL value: https://arbeidsgiver.intern.dev.nav.no/min-side-arbeidsgiver/oauth2/login - name: PROXY_LOG_LEVEL diff --git a/nais/labs-gcp.yaml b/nais/labs-gcp.yaml index 6360f41a0..75645f0d9 100644 --- a/nais/labs-gcp.yaml +++ b/nais/labs-gcp.yaml @@ -26,8 +26,6 @@ spec: env: - name: NODE_EXTRA_CA_CERTS value: /etc/ssl/ca-bundle.pem - - name: BACKEND_API_URL - value: http://demo-min-side-arbeidsgiver-api.fager - name: LOGIN_URL value: https://arbeidsgiver.ekstern.dev.nav.no/fake-login/login?issuer=selvbetjening&sub=123456789&domain=arbeidsgiver.ekstern.dev.nav.no&redirect=https://arbeidsgiver.ekstern.dev.nav.no/min-side-arbeidsgiver/ - name: PROXY_LOG_LEVEL diff --git a/nais/prod-gcp.yaml b/nais/prod-gcp.yaml index a5f650af0..88f6c6244 100644 --- a/nais/prod-gcp.yaml +++ b/nais/prod-gcp.yaml @@ -32,8 +32,6 @@ spec: env: - name: NODE_EXTRA_CA_CERTS value: /etc/ssl/ca-bundle.pem - - name: BACKEND_API_URL - value: http://min-side-arbeidsgiver-api.fager - name: LOGIN_URL value: https://arbeidsgiver.nav.no/min-side-arbeidsgiver/oauth2/login - name: PROXY_LOG_LEVEL diff --git a/server/mock/antallArbeidsforholdMock.js b/server/mock/antallArbeidsforholdMock.js index f24512d94..3ede8643d 100644 --- a/server/mock/antallArbeidsforholdMock.js +++ b/server/mock/antallArbeidsforholdMock.js @@ -1,9 +1,12 @@ export const mock = (app) => { - app.use('/min-side-arbeidsgiver/antall-arbeidsforhold', (req, res) => { - const antall = 502; - const missing = Math.floor(Math.random() * 10) === 0; - setTimeout(() => { - res.send({ first: '131488434', second: missing ? -1 : antall }); - }, 1000); - }); + app.use( + '/min-side-arbeidsgiver/arbeidsgiver-arbeidsforhold-api/antall-arbeidsforhold', + (req, res) => { + const antall = 502; + const missing = Math.floor(Math.random() * 10) === 0; + setTimeout(() => { + res.send({ first: '131488434', second: missing ? -1 : antall }); + }, 1000); + } + ); }; diff --git a/server/server.js b/server/server.js index d57498dae..b66c99765 100644 --- a/server/server.js +++ b/server/server.js @@ -20,7 +20,6 @@ const { GIT_COMMIT = '?', LOGIN_URL = 'http://localhost:8080/ditt-nav-arbeidsgiver-api/local/selvbetjening-login?redirect=http://localhost:3000/min-side-arbeidsgiver', NAIS_CLUSTER_NAME = 'local', - BACKEND_API_URL = 'http://localhost:8080', PROXY_LOG_LEVEL = 'info', MILJO = 'local', } = process.env; @@ -36,7 +35,7 @@ const log = new Proxy( transports: [ new transports.Console({ timestamp: true, - format: format.json(), + format: format.combine(format.splat(), format.json()), }), ], }), @@ -132,7 +131,7 @@ const main = async () => { changeOrigin: true, }; app.use( - '/min-side-arbeidsgiver/tiltaksgjennomforing-api/avtaler', + '/min-side-arbeidsgiver/tiltaksgjennomforing-api', tokenXMiddleware({ log: log, audience: { @@ -149,7 +148,7 @@ const main = async () => { try { if (proxyRes.statusCode >= 400) { log.warn( - `tiltaksgjennomforing-api/avtaler feilet ${proxyRes.statusCode}: ${proxyRes.statusMessage}` + `tiltaksgjennomforing-api feilet ${proxyRes.statusCode}: ${proxyRes.statusMessage}` ); return JSON.stringify([]); } @@ -162,23 +161,20 @@ const main = async () => { return JSON.stringify(data); } } catch (error) { - log.error(`tiltaksgjennomforing-api/avtaler feilet ${error}`); + log.error(`tiltaksgjennomforing-api feilet ${error}`); return JSON.stringify([]); } }), }, - pathRewrite: { - '^/min-side-arbeidsgiver/': '/', - }, target: { - dev: 'https://tiltak-proxy.dev-fss-pub.nais.io', - prod: 'https://tiltak-proxy.prod-fss-pub.nais.io', + dev: 'https://tiltak-proxy.dev-fss-pub.nais.io/tiltaksgjennomforing-api', + prod: 'https://tiltak-proxy.prod-fss-pub.nais.io/tiltaksgjennomforing-api', }[MILJO], }) ); app.use( - '/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern/antallkandidater', + '/min-side-arbeidsgiver/presenterte-kandidater-api', tokenXMiddleware({ log: log, audience: { @@ -188,15 +184,12 @@ const main = async () => { }), createProxyMiddleware({ ...proxyOptions, - pathRewrite: { - '^/min-side-arbeidsgiver/presenterte-kandidater-api/ekstern': '/ekstern', - }, target: 'http://presenterte-kandidater-api.toi', }) ); app.use( - '/min-side-arbeidsgiver/antall-arbeidsforhold', + '/min-side-arbeidsgiver/arbeidsgiver-arbeidsforhold-api', tokenXMiddleware({ log: log, audience: { @@ -206,13 +199,9 @@ const main = async () => { }), createProxyMiddleware({ ...proxyOptions, - pathRewrite: { - '^/min-side-arbeidsgiver/antall-arbeidsforhold': - '/arbeidsgiver-arbeidsforhold-api/antall-arbeidsforhold', - }, target: { - dev: 'https://aareg-innsyn-arbeidsgiver-api.dev-fss-pub.nais.io', - prod: 'https://aareg-innsyn-arbeidsgiver-api.prod-fss-pub.nais.io', + dev: 'https://aareg-innsyn-arbeidsgiver-api.dev-fss-pub.nais.io/arbeidsgiver-arbeidsforhold-api', + prod: 'https://aareg-innsyn-arbeidsgiver-api.prod-fss-pub.nais.io/arbeidsgiver-arbeidsforhold-api', }[MILJO], }) ); @@ -228,10 +217,7 @@ const main = async () => { }), createProxyMiddleware({ ...proxyOptions, - pathRewrite: { - '^/min-side-arbeidsgiver/api': '/ditt-nav-arbeidsgiver-api/api', - }, - target: BACKEND_API_URL, + target: 'http://min-side-arbeidsgiver-api.fager.svc.cluster.local/ditt-nav-arbeidsgiver-api/api', }) ); @@ -246,10 +232,7 @@ const main = async () => { }), createProxyMiddleware({ ...proxyOptions, - target: 'http://notifikasjon-bruker-api.fager.svc.cluster.local', - pathRewrite: { - '^/min-side-arbeidsgiver/notifikasjon-bruker-api': '/api/graphql', - }, + target: 'http://notifikasjon-bruker-api.fager.svc.cluster.local/api/graphql', }) ); @@ -280,24 +263,6 @@ const main = async () => { res.send(indexHtml); }); - if (MILJO === 'dev' || MILJO === 'prod') { - const gauge = new Prometheus.Gauge({ - name: 'backend_api_gw', - help: 'Hvorvidt frontend-server naar backend-server. up=1, down=0', - }); - setInterval(async () => { - try { - const res = await fetch( - `${BACKEND_API_URL}/ditt-nav-arbeidsgiver-api/internal/actuator/health` - ); - gauge.set(res.ok ? 1 : 0); - } catch (error) { - log.error(`healthcheck error: ${gauge.name}`, error); - gauge.set(0); - } - }, 60 * 1000); - } - const server = app.listen(PORT, () => { log.info(`Server listening on port ${PORT}`); }); diff --git a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts index 9c4256f20..027d5a7f1 100644 --- a/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts +++ b/src/App/Hovedside/TjenesteBoksContainer/Arbeidsforholdboks/useAntallArbeidsforholdFraAareg.ts @@ -9,7 +9,7 @@ export const useAntallArbeidsforholdFraAareg = (): number => { const { data } = useSWR( valgtOrganisasjon !== undefined ? { - url: '/min-side-arbeidsgiver/antall-arbeidsforhold', + url: '/min-side-arbeidsgiver/arbeidsgiver-arbeidsforhold-api/antall-arbeidsforhold', jurenhet: valgtOrganisasjon.organisasjon.ParentOrganizationNumber ?? '', orgnr: valgtOrganisasjon.organisasjon.OrganizationNumber, } From ab91d3cee7e9ed76eed2a19d18f22dfd9e233a41 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 13:14:07 +0200 Subject: [PATCH 20/25] try fix 404 --- server/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/server.js b/server/server.js index b66c99765..50f736501 100644 --- a/server/server.js +++ b/server/server.js @@ -232,6 +232,7 @@ const main = async () => { }), createProxyMiddleware({ ...proxyOptions, + pathRewrite: { '^/': '' }, target: 'http://notifikasjon-bruker-api.fager.svc.cluster.local/api/graphql', }) ); From 41e9e8ddab7456075325d4e5fd914f9bbe3c6fcf Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 14:42:34 +0200 Subject: [PATCH 21/25] mask format for winston --- server/server.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/server/server.js b/server/server.js index 50f736501..e7c9abc50 100644 --- a/server/server.js +++ b/server/server.js @@ -1,5 +1,4 @@ import path from 'path'; -import fetch from 'node-fetch'; import express from 'express'; import Mustache from 'mustache'; import httpProxyMiddleware, { responseInterceptor } from 'http-proxy-middleware'; @@ -20,7 +19,6 @@ const { GIT_COMMIT = '?', LOGIN_URL = 'http://localhost:8080/ditt-nav-arbeidsgiver-api/local/selvbetjening-login?redirect=http://localhost:3000/min-side-arbeidsgiver', NAIS_CLUSTER_NAME = 'local', - PROXY_LOG_LEVEL = 'info', MILJO = 'local', } = process.env; @@ -29,9 +27,17 @@ const log_events_counter = new Prometheus.Counter({ help: 'Antall log events fordelt på level', labelNames: ['level'], }); + +const maskFormat = format((info) => { + return { + ...info, + message: info.message.replace(/\d{9,}/g, (match) => '*'.repeat(match.length)), + }; +}); // proxy calls to log. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get const log = new Proxy( createLogger({ + format: maskFormat(), transports: [ new transports.Console({ timestamp: true, From f6b4074aa4d73d3974e2d48e0116f1abec33ff46 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 14:45:29 +0200 Subject: [PATCH 22/25] cleanup ubrukte env vars --- nais/dev-gcp.yaml | 4 ---- nais/experimental-labs-gcp.yaml | 6 ------ nais/labs-gcp.yaml | 4 ---- nais/prod-gcp.yaml | 4 ---- 4 files changed, 18 deletions(-) diff --git a/nais/dev-gcp.yaml b/nais/dev-gcp.yaml index db7390a09..2f96478bb 100644 --- a/nais/dev-gcp.yaml +++ b/nais/dev-gcp.yaml @@ -34,10 +34,6 @@ spec: value: /etc/ssl/ca-bundle.pem - name: LOGIN_URL value: https://arbeidsgiver.intern.dev.nav.no/min-side-arbeidsgiver/oauth2/login - - name: PROXY_LOG_LEVEL - value: debug - - name: BRUKER_API_URL - value: http://notifikasjon-bruker-api.fager.svc.cluster.local - name: GIT_COMMIT value: {{commit}} - name: MILJO diff --git a/nais/experimental-labs-gcp.yaml b/nais/experimental-labs-gcp.yaml index 90fec314c..6b560b76b 100644 --- a/nais/experimental-labs-gcp.yaml +++ b/nais/experimental-labs-gcp.yaml @@ -26,14 +26,8 @@ spec: env: - name: NODE_EXTRA_CA_CERTS value: /etc/ssl/ca-bundle.pem - - name: BACKEND_API_URL - value: http://demo-min-side-arbeidsgiver-api.fager - name: LOGIN_URL value: https://arbeidsgiver.ekstern.dev.nav.no/fake-login/login?issuer=selvbetjening&sub=123456789&domain=ekstern.dev.nav.no&redirect=https://arbeidsgiver-dev-lik.ekstern.dev.nav.no/min-side-arbeidsgiver/ - - name: PROXY_LOG_LEVEL - value: debug - - name: BRUKER_API_URL - value: http://localhost:8081 - name: GIT_COMMIT value: {{commit}} - name: MILJO diff --git a/nais/labs-gcp.yaml b/nais/labs-gcp.yaml index 75645f0d9..641240bce 100644 --- a/nais/labs-gcp.yaml +++ b/nais/labs-gcp.yaml @@ -28,10 +28,6 @@ spec: value: /etc/ssl/ca-bundle.pem - name: LOGIN_URL value: https://arbeidsgiver.ekstern.dev.nav.no/fake-login/login?issuer=selvbetjening&sub=123456789&domain=arbeidsgiver.ekstern.dev.nav.no&redirect=https://arbeidsgiver.ekstern.dev.nav.no/min-side-arbeidsgiver/ - - name: PROXY_LOG_LEVEL - value: debug - - name: BRUKER_API_URL - value: http://localhost:8081 - name: GIT_COMMIT value: {{commit}} - name: MILJO diff --git a/nais/prod-gcp.yaml b/nais/prod-gcp.yaml index 88f6c6244..1d0e74770 100644 --- a/nais/prod-gcp.yaml +++ b/nais/prod-gcp.yaml @@ -34,10 +34,6 @@ spec: value: /etc/ssl/ca-bundle.pem - name: LOGIN_URL value: https://arbeidsgiver.nav.no/min-side-arbeidsgiver/oauth2/login - - name: PROXY_LOG_LEVEL - value: debug - - name: BRUKER_API_URL - value: http://notifikasjon-bruker-api.fager.svc.cluster.local - name: GIT_COMMIT value: {{commit}} - name: MILJO From b02354238a9ea1e5e5f7cd08588aff88aad19ed8 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 14:59:06 +0200 Subject: [PATCH 23/25] cleanup --- server/server.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/server/server.js b/server/server.js index e7c9abc50..d107ab039 100644 --- a/server/server.js +++ b/server/server.js @@ -28,12 +28,11 @@ const log_events_counter = new Prometheus.Counter({ labelNames: ['level'], }); -const maskFormat = format((info) => { - return { - ...info, - message: info.message.replace(/\d{9,}/g, (match) => '*'.repeat(match.length)), - }; -}); +const maskFormat = format((info) => ({ + ...info, + message: info.message.replace(/\d{9,}/g, (match) => '*'.repeat(match.length)), +})); + // proxy calls to log. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/get const log = new Proxy( createLogger({ From e4288d0a79299af111cd5fd89f7ce90fcfeec156 Mon Sep 17 00:00:00 2001 From: Peter Brottveit Bock Date: Fri, 1 Sep 2023 15:01:23 +0200 Subject: [PATCH 24/25] bump notifikasjon-widget --- package-lock.json | 34 +++++++++++++++++----------------- package.json | 4 ++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 128a1f32c..b779a7767 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "0.1.0", "dependencies": { "@navikt/aksel-icons": "^4.9.0", - "@navikt/arbeidsgiver-notifikasjon-widget": "6.3.8", - "@navikt/bedriftsmeny": "6.10.0", + "@navikt/arbeidsgiver-notifikasjon-widget": "6.3.9", + "@navikt/bedriftsmeny": "6.10.2", "@navikt/ds-css": "^4.9.0", "@navikt/ds-icons": "3.4.3", "@navikt/ds-react": "^4.9.0", @@ -4073,9 +4073,9 @@ "license": "MIT" }, "node_modules/@navikt/arbeidsgiver-notifikasjon-widget": { - "version": "6.3.8", - "resolved": "https://npm.pkg.github.com/download/@navikt/arbeidsgiver-notifikasjon-widget/6.3.8/ed29ca6358e9e1cb53e78d5f458ad06fd7858473", - "integrity": "sha512-63Kwn5jqt7R4FUrS1d7JTrU5KkO5CeSvHXufNqRT7Sg6DXhzrqksMJMRMbj4R7kLogYOufA/R5ZNXZ8HaqW4jQ==", + "version": "6.3.9", + "resolved": "https://npm.pkg.github.com/download/@navikt/arbeidsgiver-notifikasjon-widget/6.3.9/901114d7aabc5617ed16a127739f9bde1c7fe941", + "integrity": "sha512-49UMswCOJudP5eunEuTM3mlELZpJ7uDGn0CWj6yO90/LR5vnWarfy6t5hhkKsoPDJmQ2lfnmjuswZfpMY2d8Nw==", "dependencies": { "@apollo/client": "3.6.9", "graphql": "^16.6.0" @@ -4089,9 +4089,9 @@ } }, "node_modules/@navikt/bedriftsmeny": { - "version": "6.10.0", - "resolved": "https://npm.pkg.github.com/download/@navikt/bedriftsmeny/6.10.0/376e5d17f292ef0f8d24e3bf523d5df19dada005", - "integrity": "sha512-u6DpuyvST1OqKFEYKyWss/HjsxxmLmtQ4uscYd/qRCylPtvlLVQ9evjMVuXbfWtNakTxkMu2n7GVZHN0/6WnRg==", + "version": "6.10.2", + "resolved": "https://npm.pkg.github.com/download/@navikt/bedriftsmeny/6.10.2/6321b8b35a5fd8ab506ffc13069713e36ad2adc5", + "integrity": "sha512-Bh14dZ9eSrjzKokZekig/7NCN+FxrpSLfGuItflp2U6ZzgFq7gSXySBAs9QPbe3M7JqAfinJASRCHABv2ax8tg==", "license": "MIT", "dependencies": { "@types/amplitude-js": "^8.9.4", @@ -4099,9 +4099,9 @@ "history": "^4.10.1" }, "peerDependencies": { - "@navikt/ds-css": "2 || 3 || 4", - "@navikt/ds-icons": "2 || 3", - "@navikt/ds-react": "2 || 3 || 4", + "@navikt/ds-css": ">=2", + "@navikt/ds-icons": ">=2", + "@navikt/ds-react": ">=2", "@types/react": "17 || 18", "@types/react-dom": "17 || 18", "react": "17 || 18", @@ -23280,18 +23280,18 @@ "integrity": "sha512-WOTkelI+W1VR0VvC6DyTznHcgCcYq5BTWIHU3zmPJMi2ImfmOAP768kGW8imxQ600hN9bTwnjBZixAC5pFsM6A==" }, "@navikt/arbeidsgiver-notifikasjon-widget": { - "version": "6.3.8", - "resolved": "https://npm.pkg.github.com/download/@navikt/arbeidsgiver-notifikasjon-widget/6.3.8/ed29ca6358e9e1cb53e78d5f458ad06fd7858473", - "integrity": "sha512-63Kwn5jqt7R4FUrS1d7JTrU5KkO5CeSvHXufNqRT7Sg6DXhzrqksMJMRMbj4R7kLogYOufA/R5ZNXZ8HaqW4jQ==", + "version": "6.3.9", + "resolved": "https://npm.pkg.github.com/download/@navikt/arbeidsgiver-notifikasjon-widget/6.3.9/901114d7aabc5617ed16a127739f9bde1c7fe941", + "integrity": "sha512-49UMswCOJudP5eunEuTM3mlELZpJ7uDGn0CWj6yO90/LR5vnWarfy6t5hhkKsoPDJmQ2lfnmjuswZfpMY2d8Nw==", "requires": { "@apollo/client": "3.6.9", "graphql": "^16.6.0" } }, "@navikt/bedriftsmeny": { - "version": "6.10.0", - "resolved": "https://npm.pkg.github.com/download/@navikt/bedriftsmeny/6.10.0/376e5d17f292ef0f8d24e3bf523d5df19dada005", - "integrity": "sha512-u6DpuyvST1OqKFEYKyWss/HjsxxmLmtQ4uscYd/qRCylPtvlLVQ9evjMVuXbfWtNakTxkMu2n7GVZHN0/6WnRg==", + "version": "6.10.2", + "resolved": "https://npm.pkg.github.com/download/@navikt/bedriftsmeny/6.10.2/6321b8b35a5fd8ab506ffc13069713e36ad2adc5", + "integrity": "sha512-Bh14dZ9eSrjzKokZekig/7NCN+FxrpSLfGuItflp2U6ZzgFq7gSXySBAs9QPbe3M7JqAfinJASRCHABv2ax8tg==", "requires": { "@types/amplitude-js": "^8.9.4", "fuzzysort": "^1.1.4", diff --git a/package.json b/package.json index 06df93f10..61892fd6e 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "homepage": "/min-side-arbeidsgiver", "dependencies": { "@navikt/aksel-icons": "^4.9.0", - "@navikt/arbeidsgiver-notifikasjon-widget": "6.3.8", - "@navikt/bedriftsmeny": "6.10.0", + "@navikt/arbeidsgiver-notifikasjon-widget": "6.3.9", + "@navikt/bedriftsmeny": "6.10.2", "@navikt/ds-css": "^4.9.0", "@navikt/ds-icons": "3.4.3", "@navikt/ds-react": "^4.9.0", From 3e6160b6a2df1e40427d6cfee2fe7faba55d0f72 Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Fri, 1 Sep 2023 15:14:27 +0200 Subject: [PATCH 25/25] rm ubrukt option --- server/server.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server/server.js b/server/server.js index d107ab039..0dff04868 100644 --- a/server/server.js +++ b/server/server.js @@ -122,7 +122,6 @@ const main = async () => { ); } else { const proxyOptions = { - logLevel: PROXY_LOG_LEVEL, logger: log, on: { error: (err, req, res) => {