Skip to content

Commit

Permalink
Merge pull request #4567 from navikt/feature/rydd-i-toggles
Browse files Browse the repository at this point in the history
implementer by-tiltakskode custom strategy for unleash
  • Loading branch information
sondrele authored Oct 31, 2024
2 parents 365f6c5 + 63e3561 commit 3dbf252
Show file tree
Hide file tree
Showing 16 changed files with 190 additions and 63 deletions.
1 change: 0 additions & 1 deletion frontend/mr-admin-flate/src/api/QueryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export const QueryKeys = {
brregVirksomhetUnderenheter: (id: string) => ["virksomet", id, "underenheter"],
navansatt: (rolle: NavAnsattRolle) => ["nav-ansatte", rolle],
sokNavansatt: (q: string) => ["sok-nav-ansatte", q],
features: (feature: string) => ["feature", feature],
navRegioner: () => ["navRegioner"],
personopplysninger: () => ["personopplysninger"],
opprettTilsagn: () => ["opprett-tilsagn"],
Expand Down
10 changes: 5 additions & 5 deletions frontend/mr-admin-flate/src/api/features/useFeatureToggle.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { useQuery } from "@tanstack/react-query";
import { FeatureToggleService, Toggles } from "@mr/api-client";
import { QueryKeys } from "@/api/QueryKeys";
import { FeatureToggleService, Tiltakskode, Toggles } from "@mr/api-client";

export type Features = Record<Toggles, boolean>;

/**
* Hook for å bruke en spesifikk feature toggle for å skjule eller vise funksjonalitet
* @param feature Navn på feature-toggle du vil bruke
* @param tiltakskoder Input til "by-tiltakskode"-stategi
* @returns true hvis toggle er skrudd på, eller false hvis ikke
*/
export function useFeatureToggle(feature: Toggles) {
export function useFeatureToggle(feature: Toggles, tiltakskoder: Tiltakskode[] = []) {
return useQuery({
queryKey: QueryKeys.features(feature),
queryFn: () => FeatureToggleService.getFeatureToggle({ feature }),
queryKey: ["feature", feature, tiltakskoder],
queryFn: () => FeatureToggleService.getFeatureToggle({ feature, tiltakskoder }),
throwOnError: false,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const mockFeatures: Features = {
[Toggles.MULIGHETSROMMET_ADMIN_FLATE_ENABLE_DEBUGGER]: true,
[Toggles.MULIGHETSROMMET_ADMIN_FLATE_OPPRETT_TILSAGN]: true,
[Toggles.MULIGHETSROMMET_VEILEDERFLATE_VIS_TILBAKEMELDING]: true,
[Toggles.MULIGHETSROMMET_TILTAKSTYPE_MIGRERING_DELTAKER]: true,
};

export const featureToggleHandlers = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { QueryKeys } from "./query-keys";
import { useQuery } from "@tanstack/react-query";
import { FeatureToggleService, Toggles } from "@mr/api-client";
import { FeatureToggleService, Tiltakskode, Toggles } from "@mr/api-client";

export type Features = Record<Toggles, boolean>;

/**
* Hook for å bruke en spesifikk feature toggle for å skjule eller vise funksjonalitet
* @param feature Navn på feature-toggle du vil bruke
* @param tiltakskoder Input til "by-tiltakskode"-stategi
* @returns true hvis toggle er skrudd på, eller false hvis ikke
*/
export function useFeatureToggle(feature: Toggles) {
export function useFeatureToggle(feature: Toggles, tiltakskoder: Tiltakskode[] = []) {
return useQuery({
queryKey: QueryKeys.features(feature),
queryFn: () => FeatureToggleService.getFeatureToggle({ feature }),
queryKey: ["feature", feature, tiltakskoder],
queryFn: () => FeatureToggleService.getFeatureToggle({ feature, tiltakskoder }),
throwOnError: false,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export const QueryKeys = {
tiltakById: (id: string) => ["tiltaksgjennomforing", id],
previewTiltakById: (id: string) => ["tiltaksgjennomforing", "preview", id],
},
features: (feature: string) => [feature, "feature"],
navEnheter: (statuser: NavEnhetStatus[], typer: NavEnhetType[]) => [
statuser,
typer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import { useTiltakstyperSomStotterPameldingIModia } from "@/api/queries/useTilta
import { ModiaRoute, resolveModiaRoute } from "@/apps/modia/ModiaRoute";
import { useGetTiltaksgjennomforingIdFraUrl } from "@/hooks/useGetTiltaksgjennomforingIdFraUrl";
import {
GruppetiltakDeltakerStatus,
DeltakelseGruppetiltak,
GruppetiltakDeltakerStatus,
Toggles,
VeilederflateTiltakGruppe,
} from "@mr/api-client";
import { Alert, BodyShort, Button, Heading, VStack } from "@navikt/ds-react";
import { ReactNode } from "react";
import styles from "./PameldingForGruppetiltak.module.scss";
import { useHentDeltakelseForGjennomforing } from "@/api/queries/useHentDeltakelseForGjennomforing";
import { useFeatureToggle } from "@/api/feature-toggles";

interface PameldingProps {
brukerHarRettPaaValgtTiltak: boolean;
Expand All @@ -20,41 +22,43 @@ export function PameldingForGruppetiltak({
brukerHarRettPaaValgtTiltak,
tiltak,
}: PameldingProps): ReactNode {
const { data: aktivDeltakelse } = useHentDeltakelseForGjennomforing();
const { data: brukerDeltarPaaValgtTiltak } = useHentDeltakelseForGjennomforing();
const { data: stotterPameldingIModia = [] } = useTiltakstyperSomStotterPameldingIModia();
const gjennomforingId = useGetTiltaksgjennomforingIdFraUrl();

const skalVisePameldingslenke =
brukerHarRettPaaValgtTiltak &&
tiltak.tiltakstype.tiltakskode &&
stotterPameldingIModia.includes(tiltak.tiltakstype.tiltakskode) &&
!aktivDeltakelse;
const tiltakskoder = tiltak.tiltakstype.tiltakskode ? [tiltak.tiltakstype.tiltakskode] : [];
const { data: deltakelserErMigrert } = useFeatureToggle(
Toggles.MULIGHETSROMMET_TILTAKSTYPE_MIGRERING_DELTAKER,
tiltakskoder,
);

const opprettDeltakelseRoute = resolveModiaRoute({
route: ModiaRoute.ARBEIDSMARKEDSTILTAK_OPPRETT_DELTAKELSE,
gjennomforingId,
});
const brukerKanMeldesPaaValgtTiltak =
brukerHarRettPaaValgtTiltak &&
!brukerDeltarPaaValgtTiltak &&
(deltakelserErMigrert ||
(tiltak.tiltakstype.tiltakskode &&
stotterPameldingIModia.includes(tiltak.tiltakstype.tiltakskode)));

let vedtakRoute = null;
if (aktivDeltakelse) {
vedtakRoute = resolveModiaRoute({
route: ModiaRoute.ARBEIDSMARKEDSTILTAK_DELTAKELSE,
deltakerId: aktivDeltakelse.id,
if (brukerKanMeldesPaaValgtTiltak) {
const opprettDeltakelseRoute = resolveModiaRoute({
route: ModiaRoute.ARBEIDSMARKEDSTILTAK_OPPRETT_DELTAKELSE,
gjennomforingId,
});
}

if (!skalVisePameldingslenke && !aktivDeltakelse) {
return null;
}

if (skalVisePameldingslenke) {
return (
<Button variant={"primary"} onClick={opprettDeltakelseRoute.navigate}>
Start påmelding
</Button>
);
} else if (aktivDeltakelse) {
const tekster = utledTekster(aktivDeltakelse);
}

if (brukerDeltarPaaValgtTiltak) {
const vedtakRoute = resolveModiaRoute({
route: ModiaRoute.ARBEIDSMARKEDSTILTAK_DELTAKELSE,
deltakerId: brukerDeltarPaaValgtTiltak.id,
});

const tekster = utledTekster(brukerDeltarPaaValgtTiltak);
return (
<Alert variant={tekster.variant}>
<Heading level={"2"} size="small">
Expand All @@ -77,6 +81,8 @@ export function PameldingForGruppetiltak({
</Alert>
);
}

return null;
}

interface Tekst {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const mockFeatures: Features = {
[Toggles.MULIGHETSROMMET_ADMIN_FLATE_ENABLE_DEBUGGER]: true,
[Toggles.MULIGHETSROMMET_ADMIN_FLATE_OPPRETT_TILSAGN]: true,
[Toggles.MULIGHETSROMMET_VEILEDERFLATE_VIS_TILBAKEMELDING]: true,
[Toggles.MULIGHETSROMMET_TILTAKSTYPE_MIGRERING_DELTAKER]: true,
};

export const featureToggleHandlers = [
Expand Down
10 changes: 6 additions & 4 deletions mulighetsrommet-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,15 @@ Eksempel:
$ curl localhost:8080/api/v1/innsatsgrupper -H 'Authorization: Bearer <access_token>'
```

For arrangor-flate kreves det token generert fra [mock-oauth2-server sin tokenx side for debugging av tokens](http://localhost:8081/tokenx/debugger). Les mer i readme til arrangor-flate.
For arrangor-flate kreves det token generert
fra [mock-oauth2-server sin tokenx side for debugging av tokens](http://localhost:8081/tokenx/debugger). Les mer i
readme til arrangor-flate.

### Feature toggles

Vi administrerer en del feature toggles via [NAIS og Unleash](https://doc.nais.io/addons/unleash/).
Grensesnitt for å definere toggles finner du her: https://team-mulighetsrommet-unleash-web.nav.cloud.nais.io (logg inn
med @nav-brukeren din).
Grensesnitt for å definere toggles finner du her: https://team-mulighetsrommet-unleash-web.iap.nav.cloud.nais.io
(logg inn med @nav-brukeren din).

Standard oppsett er at Unleash blir [mocket lokalt](../README.md#mocks-via-wiremock), så husk gjerne å oppdatere mocken
med nye feature toggles etter hvert som de legges til.
Expand All @@ -152,7 +154,7 @@ Hvis du heller ønsker å peke lokal applikasjon direkte mot Unleash kan du gjø
1. Konfigurer miljøvariabelen `UNLEASH_SERVER_API_URL` med riktig
URL: https://team-mulighetsrommet-unleash-api.nav.cloud.nais.io
2. Opprett et
personlig [Unleash token](https://team-mulighetsrommet-unleash-web.nav.cloud.nais.io/profile/personal-api-tokens)
personlig [Unleash token](https://team-mulighetsrommet-unleash-web.iap.nav.cloud.nais.io/profile/personal-api-tokens)
og konfigurerer miljøvariabelen `UNLEASH_SERVER_API_TOKEN` med dette tokenet
3. Start applikasjonen

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ import no.nav.mulighetsrommet.tokenprovider.CachedTokenProvider
import no.nav.mulighetsrommet.tokenprovider.M2MTokenProvider
import no.nav.mulighetsrommet.tokenprovider.createMaskinportenM2mTokenClient
import no.nav.mulighetsrommet.unleash.UnleashService
import no.nav.mulighetsrommet.unleash.strategies.ByEnhetStrategy
import no.nav.mulighetsrommet.unleash.strategies.ByNavIdentStrategy
import no.nav.mulighetsrommet.utdanning.client.UtdanningClient
import no.nav.mulighetsrommet.utdanning.db.UtdanningRepository
import no.nav.mulighetsrommet.utdanning.task.SynchronizeUtdanninger
Expand Down Expand Up @@ -350,11 +348,7 @@ private fun services(appConfig: AppConfig) = module {
single { NotificationService(get(), get(), get()) }
single { ArrangorService(get(), get()) }
single { RefusjonService(get(), get(), get(), get()) }
single {
val byEnhetStrategy = ByEnhetStrategy(get())
val byNavidentStrategy = ByNavIdentStrategy()
UnleashService(appConfig.unleash, byEnhetStrategy, byNavidentStrategy)
}
single { UnleashService(appConfig.unleash, get()) }
single<AxsysClient> {
AxsysV2ClientImpl(
appConfig.axsys.url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.ktor.server.routing.*
import io.ktor.server.util.*
import no.nav.mulighetsrommet.api.AppConfig
import no.nav.mulighetsrommet.api.plugins.getNavIdent
import no.nav.mulighetsrommet.domain.Tiltakskode
import no.nav.mulighetsrommet.unleash.FeatureToggleContext
import no.nav.mulighetsrommet.unleash.UnleashService
import org.koin.ktor.ext.inject
Expand All @@ -24,12 +25,14 @@ fun Route.featureTogglesRoute(config: AppConfig) {

route("/features") {
get {
val feature: String = call.request.queryParameters.getOrFail("feature")
val feature: String by call.parameters
val tiltakskoder = call.parameters.getAll("tiltakskoder")?.map { Tiltakskode.valueOf(it) } ?: emptyList()

val context = FeatureToggleContext(
userId = getNavIdent().value,
sessionId = call.generateSessionId(),
remoteAddress = call.request.origin.remoteAddress,
tiltakskoder = tiltakskoder,
)

val isEnabled = unleashService.isEnabled(feature, context)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package no.nav.mulighetsrommet.unleash

import no.nav.mulighetsrommet.domain.Tiltakskode

data class FeatureToggleContext(
val userId: String,
val sessionId: String,
val remoteAddress: String,
val tiltakskoder: List<Tiltakskode>,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import io.getunleash.DefaultUnleash
import io.getunleash.Unleash
import io.getunleash.UnleashContext
import io.getunleash.util.UnleashConfig
import no.nav.common.client.axsys.AxsysClient
import no.nav.mulighetsrommet.unleash.strategies.ByEnhetStrategy
import no.nav.mulighetsrommet.unleash.strategies.ByNavIdentStrategy
import no.nav.mulighetsrommet.unleash.strategies.ByTiltakskodeStrategy

class UnleashService(config: Config, byEnhetStrategy: ByEnhetStrategy, byNavidentStrategy: ByNavIdentStrategy) {
class UnleashService(config: Config, axsysClient: AxsysClient) {
private val unleash: Unleash

data class Config(
Expand All @@ -26,17 +28,25 @@ class UnleashService(config: Config, byEnhetStrategy: ByEnhetStrategy, byNaviden
.apiKey(config.token)
.environment(config.environment)
.build()
unleash = DefaultUnleash(unleashConfig, byEnhetStrategy, byNavidentStrategy)

unleash = DefaultUnleash(
unleashConfig,
ByEnhetStrategy(axsysClient),
ByNavIdentStrategy(),
ByTiltakskodeStrategy(),
)
}

fun isEnabled(feature: String, context: FeatureToggleContext): Boolean {
val ctx = UnleashContext.builder()
.userId(context.userId)
.sessionId(context.sessionId)
.remoteAddress(context.remoteAddress)
.addProperty(ByTiltakskodeStrategy.TILTAKSKODER_PARAM, context.tiltakskoder.joinToString(",") { it.name })
.build()
return unleash.isEnabled(feature, ctx)
}

fun isEnabled(feature: String): Boolean {
return unleash.isEnabled(feature)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package no.nav.mulighetsrommet.unleash.strategies

import io.getunleash.UnleashContext
import io.getunleash.strategy.Strategy
import no.nav.mulighetsrommet.domain.Tiltakskode

class ByTiltakskodeStrategy : Strategy {
companion object {
const val TILTAKSKODER_PARAM = "tiltakskoder"
}

override fun getName(): String {
return "by-tiltakskode"
}

override fun isEnabled(parameters: MutableMap<String, String>): Boolean {
return false
}

override fun isEnabled(parameters: MutableMap<String, String>, context: UnleashContext): Boolean {
val enabledTiltakskoder = parameters[TILTAKSKODER_PARAM]
.takeIf { !it.isNullOrBlank() }
?.let { parseTiltakskoder(it) }
?: return false

return context.getByName(TILTAKSKODER_PARAM)
.map { parseTiltakskoder(it) }
.map { requestedTiltakskoder ->
enabledTiltakskoder.containsAll(requestedTiltakskoder)
}
.orElse(false)
}
}

private fun parseTiltakskoder(it: String) = it.split(",").map { Tiltakskode.valueOf(it) }
8 changes: 8 additions & 0 deletions mulighetsrommet-api/src/main/resources/web/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1482,6 +1482,13 @@ paths:
required: true
schema:
$ref: "#/components/schemas/Toggles"

- in: query
name: tiltakskoder
schema:
type: array
items:
$ref: "#/components/schemas/Tiltakskode"
get:
tags:
- FeatureToggle
Expand Down Expand Up @@ -4110,6 +4117,7 @@ components:
- mulighetsrommet-veilederflate.vis-tilbakemelding
- mulighetsrommet.admin-flate.enableDebugger
- mulighetsrommet.admin-flate.opprett-tilsagn
- mulighetsrommet.tiltakstype.migrering.deltaker

PublisertRequest:
type: object
Expand Down
Loading

0 comments on commit 3dbf252

Please sign in to comment.