Skip to content

Commit

Permalink
feat(ui): scenario execution details
Browse files Browse the repository at this point in the history
  • Loading branch information
bbortt committed Feb 4, 2024
1 parent a0a87fd commit b8d48ce
Show file tree
Hide file tree
Showing 17 changed files with 455 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2006-2017 the original author or authors.
* Copyright 2006-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -223,6 +223,11 @@ public ScenarioExecution build() {
return scenarioExecution;
}

public ScenarioExecutionBuilder executionId(Long executionId) {
scenarioExecution.executionId = executionId;
return this;
}

public ScenarioExecutionBuilder startDate(Instant startDate) {
scenarioExecution.setStartDate(startDate);
return this;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2023 the original author or authors.
* Copyright 2023-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -24,9 +24,10 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.Objects;
import java.util.Optional;

import static java.util.Objects.nonNull;

/**
* Service Interface for managing {@link ScenarioAction}.
*/
Expand Down Expand Up @@ -89,8 +90,8 @@ public interface ScenarioActionService {
*/
static ScenarioAction restrictToDtoProperties(ScenarioAction scenarioAction) {
ScenarioExecution scenarioExecution = scenarioAction.getScenarioExecution();
if (!Objects.isNull(scenarioExecution)) {
scenarioAction.setScenarioExecution(ScenarioExecution.builder().scenarioName(scenarioExecution.getScenarioName()).build());
if (nonNull(scenarioExecution)) {
scenarioAction.setScenarioExecution(ScenarioExecution.builder().executionId(scenarioExecution.getExecutionId()).scenarioName(scenarioExecution.getScenarioName()).build());
}
return scenarioAction;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@
import java.util.Optional;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.citrusframework.simulator.model.ScenarioExecution.Status.UNKNOWN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentCaptor.captor;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
Expand Down Expand Up @@ -87,6 +90,7 @@ private static TestAction getTestActionWithName(String toBeReturned) {
void beforeEachSetup() {
ScenarioAction scenarioAction = new ScenarioAction();
ScenarioExecution scenarioExecution = ScenarioExecution.builder()
.executionId(1234L)
.startDate(Instant.now())
.endDate(Instant.now())
.scenarioName("scenario-name")
Expand Down Expand Up @@ -129,20 +133,31 @@ void testFindOne() {

Optional<ScenarioAction> maybeScenarioAction = fixture.findOne(scenarioActionId);

assertTrue(maybeScenarioAction.isPresent());
assertEquals(scenarioActionWithScenarioExecution, maybeScenarioAction.get());
assertThat(maybeScenarioAction)
.isPresent()
.containsSame(scenarioActionWithScenarioExecution);

verifyDtoPreparations();
}

private void verifyDtoPreparations() {
ArgumentCaptor<ScenarioExecution> scenarioExecutionArgumentCaptor = ArgumentCaptor.forClass(ScenarioExecution.class);
ArgumentCaptor<ScenarioExecution> scenarioExecutionArgumentCaptor = captor();
verify(scenarioActionWithScenarioExecution).setScenarioExecution(scenarioExecutionArgumentCaptor.capture());

ScenarioExecution expectedScenarioExecution = scenarioActionWithScenarioExecution.getScenarioExecution();
ScenarioExecution capturedScenarioExecution = scenarioExecutionArgumentCaptor.getValue();

assertEquals(expectedScenarioExecution.getScenarioName(), capturedScenarioExecution.getScenarioName());
assertThat(capturedScenarioExecution)
.hasAllNullFieldsOrPropertiesExcept(
"executionId",
"scenarioName",
"status",
"scenarioParameters",
"scenarioActions",
"scenarioMessages")
.hasFieldOrPropertyWithValue("executionId", expectedScenarioExecution.getExecutionId())
.hasFieldOrPropertyWithValue("scenarioName", expectedScenarioExecution.getScenarioName())
.hasFieldOrPropertyWithValue("status", UNKNOWN);
}

@Nested
Expand Down
76 changes: 76 additions & 0 deletions simulator-ui/src/main/webapp/app/core/util/operator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import dayjs from 'dayjs/esm';

import { sort } from './operators';

describe('operator', () => {
describe('sort', () => {
test('sorts messages by number in ascending order', () => {
const messages = [{ id: 3 }, { id: 1 }, { id: 2 }];
const sortedMessages = sort(messages, 'id');

expect(sortedMessages).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]);
});

test('sorts messages by number in descending order', () => {
const messages = [{ id: 3 }, { id: 1 }, { id: 2 }];
const sortedMessages = sort(messages, 'id', false);

expect(sortedMessages).toEqual([{ id: 3 }, { id: 2 }, { id: 1 }]);
});

test('sorts messages by string in ascending order', () => {
const messages = [{ name: 'Charlie' }, { name: 'Alice' }, { name: 'Bob' }];
const sortedMessages = sort(messages, 'name');

expect(sortedMessages).toEqual([{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }]);
});

test('sorts messages by string in descending order', () => {
const messages = [{ name: 'Charlie' }, { name: 'Alice' }, { name: 'Bob' }];
const sortedMessages = sort(messages, 'name', false);

expect(sortedMessages).toEqual([{ name: 'Charlie' }, { name: 'Bob' }, { name: 'Alice' }]);
});

test('sorts messages by dayjs date in ascending order', () => {
const messages = [{ timestamp: dayjs('2023-01-03') }, { timestamp: dayjs('2023-01-01') }, { timestamp: dayjs('2023-01-02') }];
const sortedMessages = sort(messages, 'timestamp');

expect(sortedMessages).toEqual([
{ timestamp: dayjs('2023-01-01') },
{ timestamp: dayjs('2023-01-02') },
{ timestamp: dayjs('2023-01-03') },
]);
});

test('sorts messages by dayjs date in descending order', () => {
const messages = [{ timestamp: dayjs('2023-01-03') }, { timestamp: dayjs('2023-01-01') }, { timestamp: dayjs('2023-01-02') }];
const sortedMessages = sort(messages, 'timestamp', false);

expect(sortedMessages).toEqual([
{ timestamp: dayjs('2023-01-03') },
{ timestamp: dayjs('2023-01-02') },
{ timestamp: dayjs('2023-01-01') },
]);
});

test('handles null and undefined values gracefully', () => {
const messages = [
{ name: 'Alice', id: null },
{ name: null, id: 2 },
{ name: 'Charlie', id: undefined },
{ name: 'Bob', id: 1 },
];
const sortedMessages = sort(messages, 'id');

// Depending on your sort function's implementation to handle null/undefined,
// adjust the expected result accordingly
expect(sortedMessages).toEqual([
{ name: 'Bob', id: 1 },
{ name: null, id: 2 },
{ name: 'Alice', id: null },
{ name: 'Charlie', id: undefined },
]);
});
});
});
38 changes: 38 additions & 0 deletions simulator-ui/src/main/webapp/app/core/util/operators.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import dayjs from 'dayjs/esm';

/*
* Function used to workaround https://github.com/microsoft/TypeScript/issues/16069
* es2019 alternative `const filteredArr = myArr.flatMap((x) => x ? x : []);`
Expand All @@ -7,3 +9,39 @@ export function isPresent<T>(t: T | undefined | null | void): t is T {
}

export const filterNaN = (input: number): number => (isNaN(input) ? 0 : input);

export const sort = (array: any[] | null, predicate: string, ascending: boolean = true): any[] | undefined =>
array?.sort((a: any, b: any) => {
const aValue = a[predicate];
const bValue = b[predicate];

// Handle undefined or null values
if (aValue == null && bValue == null) {
return 0;
}
if (aValue == null) {
return 1;
} // Consider undefined/null values as greater
if (bValue == null) {
return -1;
}

// Numeric comparison
if (typeof aValue === 'number' && typeof bValue === 'number') {
return ascending ? aValue - bValue : bValue - aValue;
}

// String comparison
if (typeof aValue === 'string' && typeof bValue === 'string') {
return ascending ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue);
}

// Date comparison (using dayjs objects)
if (dayjs.isDayjs(aValue) && dayjs.isDayjs(bValue)) {
return ascending ? aValue.valueOf() - bValue.valueOf() : bValue.valueOf() - aValue.valueOf();
}

// Mixed types or other types: you might want to handle these cases differently
console.error('Attempting to sort by a property that is not uniformly a number, string, or dayjs date');
return 0;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type SortParameters = { predicate: string; ascending: boolean };
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="table-responsive table-entities" id="entities" *ngIf="messageHeaders && messageHeaders.length > 0">
<div class="table-responsive table-entities" id="entities" *ngIf="sortedMessageHeaders && sortedMessageHeaders.length > 0">
<table class="table table-striped" aria-describedby="page-heading">
<thead>
<tr jhiSort [(predicate)]="predicate" [(ascending)]="ascending" (sortChange)="emitSortChange()">
Expand All @@ -20,19 +20,19 @@
<fa-icon class="p-1" icon="sort"></fa-icon>
</div>
</th>
<th *ngIf="fullDetails" scope="col" jhiSortBy="message.citrusMessageId">
<th *ngIf="standalone" scope="col" jhiSortBy="message.citrusMessageId">
<div class="d-flex">
<span jhiTranslate="citrusSimulatorApp.messageHeader.message">Message</span>
<fa-icon class="p-1" icon="sort"></fa-icon>
</div>
</th>
<th *ngIf="fullDetails" scope="col" jhiSortBy="createdDate">
<th *ngIf="standalone" scope="col" jhiSortBy="createdDate">
<div class="d-flex">
<span jhiTranslate="citrusSimulatorApp.messageHeader.createdDate">Created Date</span>
<fa-icon class="p-1" icon="sort"></fa-icon>
</div>
</th>
<th *ngIf="fullDetails" scope="col" jhiSortBy="lastModifiedDate">
<th *ngIf="standalone" scope="col" jhiSortBy="lastModifiedDate">
<div class="d-flex">
<span jhiTranslate="citrusSimulatorApp.messageHeader.lastModifiedDate">Last Modified Date</span>
<fa-icon class="p-1" icon="sort"></fa-icon>
Expand All @@ -42,19 +42,19 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let messageHeader of messageHeaders; trackBy: trackId" data-cy="entityTable">
<tr *ngFor="let messageHeader of sortedMessageHeaders; trackBy: trackId" data-cy="entityTable">
<td>
<a [routerLink]="['/message-header', messageHeader.headerId, 'view']">{{ messageHeader.headerId }}</a>
</td>
<td>{{ messageHeader.name }}</td>
<td>{{ messageHeader.value }}</td>
<td *ngIf="fullDetails">
<td *ngIf="standalone">
<div *ngIf="messageHeader.message">
<a [routerLink]="['/message', messageHeader.message.messageId, 'view']">{{ messageHeader.message.citrusMessageId }}</a>
</div>
</td>
<td *ngIf="fullDetails">{{ messageHeader.createdDate | formatMediumDatetime }}</td>
<td *ngIf="fullDetails">{{ messageHeader.lastModifiedDate | formatMediumDatetime }}</td>
<td *ngIf="standalone">{{ messageHeader.createdDate | formatMediumDatetime }}</td>
<td *ngIf="standalone">{{ messageHeader.lastModifiedDate | formatMediumDatetime }}</td>
<td class="text-end">
<div class="btn-group">
<button
Expand Down
Loading

0 comments on commit b8d48ce

Please sign in to comment.