Skip to content

Commit 62f481d

Browse files
committed
Forbedre DashboardService med optimalisering av SQL-spørringer og forbedret håndtering av feilstatus, samt oppdatering av DTO-er for bedre databehandling
1 parent 594e95e commit 62f481d

2 files changed

Lines changed: 158 additions & 67 deletions

File tree

apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java

Lines changed: 62 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@
3737
import tools.jackson.databind.node.ObjectNode;
3838

3939
import java.time.Month;
40+
import java.time.YearMonth;
4041
import java.util.Comparator;
4142
import java.util.HashMap;
4243
import java.util.HashSet;
4344
import java.util.List;
4445
import java.util.Map;
46+
import java.util.Objects;
4547
import java.util.Set;
46-
import java.util.concurrent.atomic.AtomicReference;
48+
import java.util.StringJoiner;
4749
import java.util.function.Function;
4850
import java.util.stream.Collectors;
4951

@@ -98,8 +100,7 @@ public Flux<DashboardTeamsDTO> getTeamsStatus() {
98100
.flatMapMany(teams -> bestillingRepository.findBestillingerForTeamsOrderBySistOppdatert()
99101
.groupBy(TeamFragment::getInterval)
100102
.flatMap(Flux::collectList)
101-
.flatMap(fragments -> Mono.just(
102-
Tuples.of(groupFragmentsByTeam(fragments, teams), fragments.getFirst().getInterval()))))
103+
.map(fragments -> Tuples.of(groupFragmentsByTeam(fragments, teams), fragments.getFirst().getInterval())))
103104
.map(tuple ->
104105
DashboardTeamsDTO.builder()
105106
.interval(tuple.getT2())
@@ -125,8 +126,7 @@ public Flux<DashboardOrganisasjonerDTO> getOrganisasjonerStatus() {
125126
.flatMapMany(oppslag -> bestillingRepository.findBestillingerForOrganisasjonerOrderBySistOppdatert()
126127
.groupBy(OrganisasjonFragment::getInterval)
127128
.flatMap(Flux::collectList)
128-
.flatMap(fragments -> Mono.just(
129-
Tuples.of(groupFragmentsByOrganisasjon(fragments, oppslag.getT2()), fragments.getFirst().getInterval())))
129+
.map(fragments -> Tuples.of(groupFragmentsByOrganisasjon(fragments, oppslag.getT2()), fragments.getFirst().getInterval()))
130130
.map(tuple -> DashboardOrganisasjonerDTO.builder()
131131
.interval(tuple.getT2())
132132
.organisasjoner(tuple.getT1().entrySet().stream()
@@ -194,7 +194,9 @@ private static Map<String, Set<String>> groupFragmentsByOrganisasjon(List<Organi
194194
var grouped = new HashMap<String, Set<String>>();
195195
fragments.forEach(fragment -> {
196196
var orgNummer = brukerToOrgnummer.get(fragment.getBrukerid());
197-
grouped.computeIfAbsent(orgNummer, _ -> new HashSet<>()).add(fragment.getBrukerid());
197+
if (Objects.nonNull(orgNummer)) {
198+
grouped.computeIfAbsent(orgNummer, _ -> new HashSet<>()).add(fragment.getBrukerid());
199+
}
198200
});
199201
return grouped;
200202
}
@@ -220,18 +222,12 @@ private static DashboardDollyTeamsDTO.Entry toDollyTeamEntry(DollyTeamFragment f
220222
public Flux<JsonNode> getFeilstatusSummert(int year, Month month) {
221223

222224
var filter = "%4d-%02d".formatted(year, month.getValue());
223-
return bestillingProgressRepository.findStatusColumns()
224-
.reduce(new StringBuilder(), (StringBuilder sb, String column) ->
225-
sb.append(" or lower(bp.")
226-
.append(column)
227-
.append(") like '%feil%'"))
228-
.map(kolonner -> "select b.sist_oppdatert::date bestilling_dato, bp.* " +
229-
"from bestilling b " +
230-
"join bestilling_progress bp on bp.bestilling_id = b.id " +
231-
"where to_char(b.sist_oppdatert, 'YYYY-MM') = :range " +
232-
"and (" +
233-
kolonner.substring(4) +
234-
") order by bestilling_dato")
225+
return buildFeilWhereFragment()
226+
.map(feilFilter -> "select b.sist_oppdatert::date bestilling_dato, bp.* " +
227+
"from bestilling b " +
228+
"join bestilling_progress bp on bp.bestilling_id = b.id " +
229+
"where to_char(b.sist_oppdatert, 'YYYY-MM') = :range " +
230+
"and (" + feilFilter + ") order by bestilling_dato")
235231
.flatMapMany(query -> entityTemplate.getDatabaseClient()
236232
.sql(query)
237233
.bind("range", filter)
@@ -246,18 +242,12 @@ public Flux<JsonNode> getFeilstatusSummert(int year, Month month) {
246242
public Flux<JsonNode> getFeilstatusDetaljert(int year, Month month, int day) {
247243

248244
var filter = "%4d-%02d-%02d".formatted(year, month.getValue(), day);
249-
return bestillingProgressRepository.findStatusColumns()
250-
.reduce(new StringBuilder(), (StringBuilder sb, String column) ->
251-
sb.append(" or lower(bp.")
252-
.append(column)
253-
.append(") like '%feil%'"))
254-
.map(kolonner -> "select b.sist_oppdatert, bp.* " +
255-
"from bestilling b " +
256-
"join bestilling_progress bp on bp.bestilling_id = b.id " +
257-
"where to_char(b.sist_oppdatert, 'YYYY-MM-DD') = :range " +
258-
"and (" +
259-
kolonner.substring(4) +
260-
") order by b.sist_oppdatert")
245+
return buildFeilWhereFragment()
246+
.map(feilFilter -> "select b.sist_oppdatert, bp.* " +
247+
"from bestilling b " +
248+
"join bestilling_progress bp on bp.bestilling_id = b.id " +
249+
"where to_char(b.sist_oppdatert, 'YYYY-MM-DD') = :range " +
250+
"and (" + feilFilter + ") order by b.sist_oppdatert")
261251
.flatMapMany(query -> entityTemplate.getDatabaseClient()
262252
.sql(query)
263253
.bind("range", filter)
@@ -268,42 +258,43 @@ public Flux<JsonNode> getFeilstatusDetaljert(int year, Month month, int day) {
268258
.flatMap(this::tilFeilJson);
269259
}
270260

261+
private Mono<String> buildFeilWhereFragment() {
262+
return bestillingProgressRepository.findStatusColumns()
263+
.reduce(new StringJoiner(" or "), (joiner, column) ->
264+
joiner.add("lower(bp." + column + ") like '%feil%'"))
265+
.map(StringJoiner::toString);
266+
}
267+
271268
private Mono<JsonNode> tilFeilstatusSummert(List<BestillingProgressDTO> bestillingProgressDTOS) {
272269

273-
var resultat = new AtomicReference<>(new HashMap<>());
270+
Map<String, Object> resultat = new HashMap<>();
274271
bestillingProgressDTOS.forEach(progress -> {
275272
var kilde = (ObjectNode) jsonMapper.valueToTree(progress);
276273

277274
for (var navn : kilde.propertyNames()) {
278275
var verdi = kilde.get(navn);
279276
if (!IDENTITETSFELT.contains(navn)) {
280277
if ("bestillingDato".equals(navn)) {
281-
resultat.get().putIfAbsent(navn, verdi);
278+
resultat.putIfAbsent(navn, verdi);
282279
} else if (verdi.isString() && verdi.asString().toLowerCase().contains("feil")) {
283-
if (resultat.get().containsKey(navn)) {
284-
var teller = (Integer) resultat.get().get(navn);
285-
resultat.get().put(navn, teller + 1);
286-
} else {
287-
resultat.get().put(navn, 1);
288-
}
280+
resultat.merge(navn, 1, (existing, _) -> (Integer) existing + 1);
289281
}
290282
}
291283
}
292284
});
293-
return Flux.fromIterable(resultat.get().entrySet())
285+
var summert = resultat.entrySet().stream()
294286
.collect(Collectors.toMap(entry -> {
295-
if (((String) entry.getKey()).contains("Status")) {
296-
return ((String) entry.getKey())
297-
.replace("Status", "Feil");
298-
} else if (entry.getKey().equals("feil")) {
287+
var key = entry.getKey();
288+
if (key.contains("Status")) {
289+
return key.replace("Status", "Feil");
290+
} else if ("feil".equals(key)) {
299291
return "andreFeil";
300292
} else {
301-
return entry.getKey();
293+
return key;
302294
}
303-
}
304-
, Map.Entry::getValue))
305-
.flatMap(summert -> summert.isEmpty() ? Mono.empty() :
306-
Mono.just(jsonMapper.valueToTree(summert)));
295+
},
296+
Map.Entry::getValue));
297+
return summert.isEmpty() ? Mono.empty() : Mono.just(jsonMapper.valueToTree(summert));
307298
}
308299

309300
private Mono<JsonNode> tilFeilJson(BestillingProgressDTO progress) {
@@ -380,25 +371,29 @@ public Flux<DashboardOversiktDTO> getPerioderOversikt() {
380371
return bestillingRepository.findByAvailIntervals()
381372
.groupBy(OversiktFragment::getMaaned)
382373
.flatMap(Flux::collectList)
383-
.map(fragmenter -> DashboardOversiktDTO.builder()
384-
.aarManed(fragmenter.getFirst().getMaaned())
385-
.aar(Integer.parseInt(fragmenter.getFirst().getMaaned().substring(0, 4)))
386-
.maaned(Month.of(Integer.parseInt(fragmenter.getFirst().getMaaned().substring(5))))
387-
.totaltAntallPersoner(fragmenter.stream()
388-
.map(OversiktFragment::getAntall)
389-
.mapToInt(Long::intValue)
390-
.sum())
391-
.nye(fragmenter.stream()
392-
.filter(fragment -> "NYBESTILLING".equals(fragment.getGjenopprettstatus()))
393-
.map(OversiktFragment::getAntall)
394-
.mapToInt(Long::intValue)
395-
.sum())
396-
.gjenopprettede(fragmenter.stream()
397-
.filter(fragment -> "GJENOPPRETTING".equals(fragment.getGjenopprettstatus()))
398-
.map(OversiktFragment::getAntall)
399-
.mapToInt(Long::intValue)
400-
.sum())
401-
.build())
374+
.map(fragmenter -> {
375+
var aarManed = fragmenter.getFirst().getMaaned();
376+
var yearMonth = YearMonth.parse(aarManed);
377+
return DashboardOversiktDTO.builder()
378+
.aarManed(aarManed)
379+
.aar(yearMonth.getYear())
380+
.maaned(yearMonth.getMonth())
381+
.totaltAntallPersoner(fragmenter.stream()
382+
.map(OversiktFragment::getAntall)
383+
.mapToInt(Long::intValue)
384+
.sum())
385+
.nye(fragmenter.stream()
386+
.filter(fragment -> "NYBESTILLING".equals(fragment.getGjenopprettstatus()))
387+
.map(OversiktFragment::getAntall)
388+
.mapToInt(Long::intValue)
389+
.sum())
390+
.gjenopprettede(fragmenter.stream()
391+
.filter(fragment -> "GJENOPPRETTING".equals(fragment.getGjenopprettstatus()))
392+
.map(OversiktFragment::getAntall)
393+
.mapToInt(Long::intValue)
394+
.sum())
395+
.build();
396+
})
402397
.sort(Comparator.comparing(DashboardOversiktDTO::getAarManed).reversed());
403398
}
404399
}

apps/dolly-backend/src/test/java/no/nav/dolly/service/DashboardServiceTest.java

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import no.nav.dolly.domain.projection.DollyTeam2Fragment;
1515
import no.nav.dolly.domain.projection.DollyTeamFragment;
1616
import no.nav.dolly.domain.projection.OrganisasjonFragment;
17+
import no.nav.dolly.domain.projection.OversiktFragment;
1718
import no.nav.dolly.domain.projection.TeamFragment;
1819
import no.nav.dolly.repository.BestillingRepository;
1920
import no.nav.dolly.repository.BrukerRepository;
@@ -27,6 +28,7 @@
2728
import reactor.test.StepVerifier;
2829

2930
import java.time.LocalDate;
31+
import java.time.Month;
3032
import java.util.List;
3133

3234
import static org.assertj.core.api.Assertions.assertThat;
@@ -425,6 +427,26 @@ void shouldSortOrganisasjonerByNavnAlphabetically() {
425427
.verifyComplete();
426428
}
427429

430+
@Test
431+
void shouldSkipOrganisasjonFragmentsWithNoBrukerserviceMatch() {
432+
when(altinn3TilgangServiceConsumer.getOrganisasjoner()).thenReturn(Flux.empty());
433+
when(brukerServiceConsumer.getAlleBrukere()).thenReturn(Flux.just(
434+
BrukerDTO.builder().id("kjent").organisasjonsnummer("123456789").build()
435+
));
436+
when(bestillingRepository.findBestillingerForOrganisasjonerOrderBySistOppdatert())
437+
.thenReturn(Flux.just(
438+
organisasjonFragment(INTERVAL_1, "kjent"),
439+
organisasjonFragment(INTERVAL_1, "ukjent")
440+
));
441+
442+
StepVerifier.create(dashboardService.getOrganisasjonerStatus())
443+
.assertNext(dto -> {
444+
assertThat(dto.getOrganisasjoner()).hasSize(1);
445+
assertThat(dto.getTotaltAntallOrganisasjoner()).isEqualTo(1);
446+
})
447+
.verifyComplete();
448+
}
449+
428450
// ── getDollyTeamsStatus ──────────────────────────────────────────────────
429451

430452
@Test
@@ -528,6 +550,72 @@ void shouldSortDollyTeamsStatusByIntervalDescending() {
528550
assertThat(results.get(1).getInterval()).isEqualTo(INTERVAL_1);
529551
}
530552

553+
// ── getPerioderOversikt ──────────────────────────────────────────────────
554+
555+
@Test
556+
void shouldReturnEmptyPerioderWhenNoIntervals() {
557+
when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.empty());
558+
559+
StepVerifier.create(dashboardService.getPerioderOversikt())
560+
.verifyComplete();
561+
}
562+
563+
@Test
564+
void shouldParseAarManedIntoAarAndMaaned() {
565+
when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.just(
566+
oversiktFragment("2024-03", 10L, "NYBESTILLING")
567+
));
568+
569+
StepVerifier.create(dashboardService.getPerioderOversikt())
570+
.assertNext(dto -> {
571+
assertThat(dto.getAarManed()).isEqualTo("2024-03");
572+
assertThat(dto.getAar()).isEqualTo(2024);
573+
assertThat(dto.getMaaned()).isEqualTo(Month.MARCH);
574+
})
575+
.verifyComplete();
576+
}
577+
578+
@Test
579+
void shouldSumTotaltAntallPersonerForSamePeriod() {
580+
when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.just(
581+
oversiktFragment("2024-01", 5L, "NYBESTILLING"),
582+
oversiktFragment("2024-01", 3L, "GJENOPPRETTING")
583+
));
584+
585+
StepVerifier.create(dashboardService.getPerioderOversikt())
586+
.assertNext(dto -> assertThat(dto.getTotaltAntallPersoner()).isEqualTo(8))
587+
.verifyComplete();
588+
}
589+
590+
@Test
591+
void shouldSumNyeAndGjenopprettedeByGjenopprettstatus() {
592+
when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.just(
593+
oversiktFragment("2024-01", 4L, "NYBESTILLING"),
594+
oversiktFragment("2024-01", 2L, "GJENOPPRETTING"),
595+
oversiktFragment("2024-01", 1L, "UKJENT")
596+
));
597+
598+
StepVerifier.create(dashboardService.getPerioderOversikt())
599+
.assertNext(dto -> {
600+
assertThat(dto.getNye()).isEqualTo(4);
601+
assertThat(dto.getGjenopprettede()).isEqualTo(2);
602+
})
603+
.verifyComplete();
604+
}
605+
606+
@Test
607+
void shouldSortPerioderOversiktByAarManedDescending() {
608+
when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.just(
609+
oversiktFragment("2024-01", 1L, "NYBESTILLING"),
610+
oversiktFragment("2024-02", 1L, "NYBESTILLING")
611+
));
612+
613+
StepVerifier.create(dashboardService.getPerioderOversikt())
614+
.assertNext(dto -> assertThat(dto.getAarManed()).isEqualTo("2024-02"))
615+
.assertNext(dto -> assertThat(dto.getAarManed()).isEqualTo("2024-01"))
616+
.verifyComplete();
617+
}
618+
531619
// ── helpers ──────────────────────────────────────────────────────────────
532620

533621
private static BestillingerFragment fragment(LocalDate dato, Long personer,
@@ -563,4 +651,12 @@ private static DollyTeamFragment dollyTeamFragment(String interval, String navn,
563651
.informasjon(navn + "|" + beskrivelse + "|" + brukerid)
564652
.build();
565653
}
654+
655+
private static OversiktFragment oversiktFragment(String maaned, Long antall, String gjenopprettstatus) {
656+
return OversiktFragment.builder()
657+
.maaned(maaned)
658+
.antall(antall)
659+
.gjenopprettstatus(gjenopprettstatus)
660+
.build();
661+
}
566662
}

0 commit comments

Comments
 (0)