Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[backend/frontend] Add quick filters on scenarios injects & simulations injects #1383

Merged
merged 3 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class InjectOutput {
private String id;

@JsonProperty("inject_title")
@NotBlank
private String title;

@JsonProperty("inject_enabled")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package io.openbas.rest;

import io.openbas.IntegrationTest;
import io.openbas.database.model.Inject;
import io.openbas.database.model.InjectorContract;
import io.openbas.database.model.Scenario;
import io.openbas.database.repository.InjectRepository;
import io.openbas.database.repository.InjectorContractRepository;
import io.openbas.database.repository.ScenarioRepository;
import io.openbas.utils.fixtures.PaginationFixture;
import io.openbas.utils.mockUser.WithMockAdminUser;
import io.openbas.utils.pagination.SearchPaginationInput;
import io.openbas.utils.pagination.SortField;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.util.ArrayList;
import java.util.List;

import static io.openbas.database.model.Filters.FilterOperator.contains;
import static io.openbas.injectors.email.EmailContract.EMAIL_DEFAULT;
import static io.openbas.rest.scenario.ScenarioApi.SCENARIO_URI;
import static io.openbas.utils.JsonUtils.asJsonString;
import static io.openbas.utils.fixtures.InjectFixture.getInjectForEmailContract;
import static io.openbas.utils.fixtures.ScenarioFixture.createDefaultCrisisScenario;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@TestInstance(PER_CLASS)
public class ScenarioInjectApiSearchTest extends IntegrationTest {

@Autowired
private MockMvc mvc;

@Autowired
private InjectRepository injectRepository;
@Autowired
private InjectorContractRepository injectorContractRepository;
@Autowired
private ScenarioRepository scenarioRepository;

private static final List<String> INJECT_IDS = new ArrayList<>();
private static String SCENARIO_ID;
private static String EMAIL_INJECTOR_CONTRACT_ID;

@BeforeAll
void beforeAll() {
InjectorContract injectorContract = this.injectorContractRepository.findById(EMAIL_DEFAULT).orElseThrow();
EMAIL_INJECTOR_CONTRACT_ID = injectorContract.getInjector().getId();

Scenario scenario = createDefaultCrisisScenario();
Scenario scenarioSaved = this.scenarioRepository.save(scenario);
SCENARIO_ID = scenarioSaved.getId();

Inject injectDefaultEmail = getInjectForEmailContract(injectorContract);
injectDefaultEmail.setScenario(scenarioSaved);
injectDefaultEmail.setTitle("Inject default email");
injectDefaultEmail.setDependsDuration(1L);
Inject injectDefaultEmailSaved = this.injectRepository.save(injectDefaultEmail);
INJECT_IDS.add(injectDefaultEmailSaved.getId());

Inject injectDefaultGlobal = getInjectForEmailContract(injectorContract);
injectDefaultGlobal.setScenario(scenarioSaved);
injectDefaultGlobal.setTitle("Inject global email");
Inject injectDefaultGlobalSaved = this.injectRepository.save(injectDefaultGlobal);
INJECT_IDS.add(injectDefaultGlobalSaved.getId());
}

@AfterAll
void afterAll() {
this.injectRepository.deleteAllById(INJECT_IDS);
this.scenarioRepository.deleteById(SCENARIO_ID);
}

@Nested
@WithMockAdminUser
@DisplayName("Retrieving injects")
class RetrievingInjects {
// -- PREPARE --

@Nested
@DisplayName("Searching page of injects")
class SearchingPageOfInjects {

@Test
@DisplayName("Retrieving first page of injects by textsearch")
void given_working_search_input_should_return_a_page_of_injects() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.getDefault().textSearch("default").build();

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.numberOfElements").value(1));
}

@Test
@DisplayName("Not retrieving first page of injects by textsearch")
void given_not_working_search_input_should_return_a_page_of_injects() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.getDefault().textSearch("wrong").build();

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.numberOfElements").value(0));
}
}

@Nested
@DisplayName("Sorting page of injects")
class SortingPageOfInjects {

@Test
@DisplayName("Sorting page of injects by name")
void given_sorting_input_by_name_should_return_a_page_of_injects_sort_by_name() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.getDefault()
.sorts(List.of(SortField.builder().property("inject_title").build()))
.build();

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.content.[0].inject_title").value("Inject default email"))
.andExpect(jsonPath("$.content.[1].inject_title").value("Inject global email"));
}

@Test
@DisplayName("Sorting page of injects by updated at")
void given_sorting_input_by_updated_at_should_return_a_page_of_injects_sort_by_updated_at()
throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.getDefault()
.sorts(List.of(SortField.builder().property("inject_depends_duration").direction("asc").build()))
.build();

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.content.[0].inject_title").value("Inject global email"))
.andExpect(jsonPath("$.content.[1].inject_title").value("Inject default email"));
}
}

@Nested
@DisplayName("Filtering page of injects")
class FilteringPageOfInjects {

@Test
@DisplayName("Filtering page of injects by name")
void given_filter_input_by_name_should_return_a_page_of_injects_filter_by_name() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.simpleFilter(
"inject_title", "email", contains
);

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.numberOfElements").value(2));
}

@Test
@DisplayName("Filtering page of injects by injector contract")
void given_filter_input_by_injector_contract_should_return_a_page_of_injects_filter_by_injector_contract() throws Exception {
SearchPaginationInput searchPaginationInput = PaginationFixture.simpleFilter(
"inject_injector_contract", EMAIL_INJECTOR_CONTRACT_ID, contains
);

mvc.perform(post(SCENARIO_URI + "/" + SCENARIO_ID + "/injects/simple")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(searchPaginationInput)))
.andExpect(status().is2xxSuccessful())
.andExpect(jsonPath("$.numberOfElements").value(2));
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand All @@ -35,27 +34,6 @@ private SchemaUtils() {
Email.class
);

public static final Class<?>[] BASE_CLASSES = {
byte.class,
short.class,
int.class,
long.class,
float.class,
double.class,
char.class,
boolean.class,
Byte.class,
Short.class,
Integer.class,
Long.class,
Float.class,
Double.class,
Character.class,
Boolean.class,
String.class,
Instant.class,
};

private static final ConcurrentHashMap<Class<?>, List<PropertySchema>> cacheMap = new ConcurrentHashMap<>();

// -- SCHEMA --
Expand Down
33 changes: 23 additions & 10 deletions openbas-front/src/actions/injects/Inject.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import type { Inject, InjectExpectation } from '../../utils/api-types';

export type InjectInput = {
inject_injector_contract: { id: string, type: string };
inject_tags: string[];
inject_depends_duration_days: number;
inject_depends_duration_hours: number;
inject_depends_duration_minutes: number;
inject_depends_duration_seconds: number;
};
import type { Inject, InjectExpectation, InjectOutput } from '../../utils/api-types';

export type InjectStore = Omit<Inject, 'inject_tags' | 'inject_content' | 'inject_injector_contract' | 'inject_teams' | 'inject_exercise' | 'inject_scenario'> & {
inject_tags: string[] | undefined;
Expand All @@ -17,11 +8,33 @@ export type InjectStore = Omit<Inject, 'inject_tags' | 'inject_content' | 'injec
// as we don't know the type of the content of a contract we need to put any here
// eslint-disable-next-line @typescript-eslint/no-explicit-any
injector_contract_content_parsed: any
convertedContent: {
label: Record<string, string>
config: {
expose: boolean
}
}
} & Inject['inject_injector_contract']
inject_exercise?: string
inject_scenario?: string
};

export type InjectorContractConvertedContent = {
label: Record<string, string>
config: {
expose: boolean
}
};

export type InjectOutputType = InjectOutput & {
inject_injector_contract: {
// as we don't know the type of the content of a contract we need to put any here
// eslint-disable-next-line @typescript-eslint/no-explicit-any
injector_contract_content_parsed: any
convertedContent: InjectorContractConvertedContent
} & Inject['inject_injector_contract']
};

export type InjectExpectationStore = Omit<InjectExpectation, 'inject_expectation_team', 'inject_expectation_inject'> & {
inject_expectation_team: string | undefined;
inject_expectation_inject: string | undefined;
Expand Down
12 changes: 11 additions & 1 deletion openbas-front/src/actions/injects/inject-action.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Dispatch } from 'redux';
import { getReferential, simpleCall, simplePostCall } from '../../utils/Action';
import type { Exercise, Scenario } from '../../utils/api-types';
import type { Exercise, Scenario, SearchPaginationInput } from '../../utils/api-types';
import * as schema from '../Schema';

export const testInject = (injectId: string) => {
Expand All @@ -21,9 +21,19 @@ export const fetchExerciseInjectsSimple = (exerciseId: Exercise['exercise_id'])
return getReferential(schema.arrayOfInjects, uri)(dispatch);
};

export const searchExerciseInjectsSimple = (exerciseId: Exercise['exercise_id'], input: SearchPaginationInput) => {
const uri = `/api/exercises/${exerciseId}/injects/simple`;
return simplePostCall(uri, input);
};

// -- SCENARIOS --

export const fetchScenarioInjectsSimple = (scenarioId: Scenario['scenario_id']) => (dispatch: Dispatch) => {
const uri = `/api/scenarios/${scenarioId}/injects/simple`;
return getReferential(schema.arrayOfInjects, uri)(dispatch);
};

export const searchScenarioInjectsSimple = (scenarioId: Scenario['scenario_id'], input: SearchPaginationInput) => {
const uri = `/api/scenarios/${scenarioId}/injects/simple`;
return simplePostCall(uri, input);
};
8 changes: 0 additions & 8 deletions openbas-front/src/actions/scenarios/scenario-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type {
ScenarioInput,
ScenarioRecurrenceInput,
ScenarioTeamPlayersEnableInput,
ScenarioUpdateTagsInput,
ScenarioUpdateTeamsInput,
SearchPaginationInput,
Team,
Expand Down Expand Up @@ -78,13 +77,6 @@ export const duplicateScenario = (scenarioId: string) => (dispatch: Dispatch) =>
return postReferential(scenario, uri, null)(dispatch);
};

// -- TAGS --

export const updateScenarioTags = (scenarioId: Scenario['scenario_id'], data: ScenarioUpdateTagsInput) => {
const uri = `${SCENARIO_URI}/${scenarioId}/tags`;
return putReferential(scenario, uri, data);
};

// -- TEAMS --

export const fetchScenarioTeams = (scenarioId: Scenario['scenario_id']) => (dispatch: Dispatch) => {
Expand Down
21 changes: 14 additions & 7 deletions openbas-front/src/admin/components/common/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import type {
LessonsSendInput,
Objective,
ObjectiveInput,
SearchPaginationInput,
Team,
TeamCreateInput,
Variable,
VariableInput,
} from '../../../utils/api-types';
import type { UserStore } from '../teams/players/Player';
import type { InjectStore } from '../../../actions/injects/Inject';
import type { InjectOutputType, InjectStore } from '../../../actions/injects/Inject';
import { Page } from '../../../components/common/queryable/Page';

export type PermissionsContextType = {
permissions: { readOnly: boolean, canWrite: boolean, isRunning: boolean }
Expand Down Expand Up @@ -69,8 +71,9 @@ export type TeamContextType = {
};

export type InjectContextType = {
onAddInject: (inject: Inject) => Promise<{ result: string }>,
onUpdateInject: (injectId: Inject['inject_id'], inject: Inject) => Promise<{ result: string }>,
searchInjects: (input: SearchPaginationInput) => Promise<{ data: Page<InjectOutputType> }>,
onAddInject: (inject: Inject) => Promise<{ result: string, entities: { injects: Record<string, InjectStore> } }>,
onUpdateInject: (injectId: Inject['inject_id'], inject: Inject) => Promise<{ result: string, entities: { injects: Record<string, InjectStore> } }>,
onUpdateInjectTrigger?: (injectId: Inject['inject_id']) => void,
onUpdateInjectActivation: (injectId: Inject['inject_id'], injectEnabled: { inject_enabled: boolean }) => Promise<{
result: string,
Expand Down Expand Up @@ -156,11 +159,15 @@ export const TeamContext = createContext<TeamContextType>({
},
});
export const InjectContext = createContext<InjectContextType>({
onAddInject(_inject: Inject): Promise<{ result: string }> {
return Promise.resolve({ result: '' });
searchInjects(_: SearchPaginationInput): Promise<{ data: Page<InjectOutputType> }> {
return new Promise<{ data: Page<InjectOutputType> }>(() => {
});
},
onUpdateInject(_injectId: Inject['inject_id'], _inject: Inject): Promise<{ result: string }> {
return Promise.resolve({ result: '' });
onAddInject(_inject: Inject): Promise<{ result: string, entities: { injects: Record<string, InjectStore> } }> {
return Promise.resolve({ result: '', entities: { injects: {} } });
},
onUpdateInject(_injectId: Inject['inject_id'], _inject: Inject): Promise<{ result: string, entities: { injects: Record<string, InjectStore> } }> {
return Promise.resolve({ result: '', entities: { injects: {} } });
},
onUpdateInjectTrigger(_injectId: Inject['inject_id']): void {
},
Expand Down
Loading