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

[NU-1805] Scenario labels support #6766

Merged
merged 37 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5ee8564
Scenario labels support
mateuszkp96 Aug 21, 2024
1046bbb
style fixes
Dzuming Aug 21, 2024
e60a9f9
labels validation
mateuszkp96 Aug 21, 2024
647aab0
Scenario labels validation
mateuszkp96 Aug 23, 2024
12a2941
QS
mateuszkp96 Aug 27, 2024
fb55aad
Labels validation via endpoint, multiple validation rules
mateuszkp96 Sep 2, 2024
452c0d6
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 3, 2024
3e3a567
QS
mateuszkp96 Sep 3, 2024
df3d57e
Fixes
mateuszkp96 Sep 5, 2024
409d603
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 5, 2024
c6851fb
Fixes
mateuszkp96 Sep 5, 2024
bdac2a9
Fixes
mateuszkp96 Sep 5, 2024
8c8bebd
Fixes
mateuszkp96 Sep 6, 2024
816027d
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 6, 2024
68dcfa4
Review fixes
mateuszkp96 Sep 9, 2024
a36059f
make input value uncontrolled
Dzuming Sep 10, 2024
a4227a1
Migration API with labels, permission handling, e2e tests
mateuszkp96 Sep 10, 2024
2280d5f
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 10, 2024
5d83912
Updated snapshots (#6831)
github-actions[bot] Sep 10, 2024
d9f6045
Cypress test fix
mateuszkp96 Sep 10, 2024
d2d17fe
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 12, 2024
f92dad1
Review fixes
mateuszkp96 Sep 16, 2024
1252c1f
fix truncate popover opening scenario on close
Dzuming Sep 16, 2024
d45691d
Review fixes
mateuszkp96 Sep 17, 2024
0a80e5d
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 17, 2024
8f6c204
Fixes
mateuszkp96 Sep 17, 2024
d6e3597
Fix
mateuszkp96 Sep 17, 2024
2a68882
Cypress fix
mateuszkp96 Sep 17, 2024
4f18630
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 17, 2024
a847d80
Updated snapshots (#6881)
github-actions[bot] Sep 17, 2024
fe206b4
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 17, 2024
b60019e
Fixes
mateuszkp96 Sep 17, 2024
99e172f
Review fixes
mateuszkp96 Sep 18, 2024
f8ef9d4
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 18, 2024
4ff0630
Review fixes
mateuszkp96 Sep 18, 2024
2b072d8
Merge branch 'staging' into scenario-labels-support
mateuszkp96 Sep 18, 2024
42c8002
Updated snapshots (#6903)
github-actions[bot] Sep 18, 2024
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 @@ -430,6 +430,10 @@ object ProcessCompilationError {
extends ProcessCompilationError
with ScenarioPropertiesError

final case class ScenarioLabelValidationError(label: String, description: String)
extends ProcessCompilationError
with ScenarioPropertiesError

final case class SpecificDataValidationError(paramName: ParameterName, message: String)
extends ProcessCompilationError
with ScenarioPropertiesError
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 92 additions & 0 deletions designer/client/cypress/e2e/labels.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
describe("Scenario labels", () => {
const seed = "process";

before(() => {
cy.deleteAllTestProcesses({ filter: seed, force: true });
});

beforeEach(() => {
cy.mockWindowDate();
});

afterEach(() => {
cy.deleteAllTestProcesses({ filter: seed, force: true });
});

describe("designer", () => {
it("should allow to set labels for new process", () => {
cy.visitNewProcess(seed).as("processName");

cy.intercept("PUT", "/api/processes/*").as("save");

cy.intercept("POST", "/api/scenarioLabels/validation").as("labelvalidation");

cy.get("[data-testid=AddLabel]").should("be.visible").click();

cy.get("[data-testid=LabelInput]").as("labelInput");

cy.get("@labelInput").should("be.visible").click().type("tagX");

cy.wait("@labelvalidation");

cy.get('.MuiAutocomplete-popper li[data-option-index="0"]').contains('Add label "tagX"').click();

cy.get("[data-testid=scenario-label-0]").should("be.visible").contains("tagX");

cy.get("@labelInput").should("be.visible").click().type("tag2");

cy.wait("@labelvalidation");

cy.get('.MuiAutocomplete-popper li[data-option-index="0"]').contains('Add label "tag2"').click();

cy.get("@labelInput").type("{enter}");

cy.get("[data-testid=scenario-label-1]").should("be.visible").contains("tag2");

cy.contains(/^save/i).should("be.enabled").click();
cy.contains(/^ok$/i).should("be.enabled").click();
cy.wait("@save").its("response.statusCode").should("eq", 200);

cy.viewport(1500, 800);

cy.get("[data-testid=scenario-label-0]").should("be.visible").contains("tag2");
cy.get("[data-testid=scenario-label-1]").should("be.visible").contains("tagX");

cy.get("@labelInput").should("be.visible").click().type("very long tag");

cy.wait("@labelvalidation").then((_) => cy.wait(100));

cy.get("@labelInput").should("be.visible").contains("Incorrect value 'very long tag'");

cy.contains(/^save/i).should("be.disabled");
});

it("should show labels for scenario", () => {
cy.visitNewProcess(seed).then((processName) => cy.addLabelsToNewProcess(processName, ["tag1", "tag3"]));

cy.viewport(1500, 800);

cy.get("[data-testid=scenario-label-0]").should("be.visible").contains("tag1");
cy.get("[data-testid=scenario-label-1]").should("be.visible").contains("tag3");
});
});

describe("scenario list", () => {
it("should allow to filter scenarios by label", () => {
cy.visitNewProcess(seed).then((processName) => cy.addLabelsToNewProcess(processName, ["tag1", "tag3"]));
cy.visitNewProcess(seed).then((processName) => cy.addLabelsToNewProcess(processName, ["tag2", "tag3"]));
cy.visitNewProcess(seed).then((processName) => cy.addLabelsToNewProcess(processName, ["tag4"]));
cy.visitNewProcess(seed);

cy.visit("/");

cy.contains("button", /label/i).click();

cy.get("ul[role='menu']").within(() => {
cy.contains(/tag2/i).click();
});

cy.contains(/1 of the 4 rows match the filters/i).should("be.visible");
});
});
});
23 changes: 23 additions & 0 deletions designer/client/cypress/support/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ declare global {
importTestProcess: typeof importTestProcess;
visitNewProcess: typeof visitNewProcess;
visitNewFragment: typeof visitNewFragment;
addLabelsToNewProcess: typeof addLabelsToNewProcess;
postFormData: typeof postFormData;
visitProcess: typeof visitProcess;
getNode: typeof getNode;
Expand Down Expand Up @@ -93,6 +94,26 @@ function visitNewFragment(name?: string, fixture?: string, category?: string) {
});
}

function addLabelsToNewProcess(name?: string, labels?: string[]) {
return cy.visitProcess(name).then((processName) => {
cy.intercept("PUT", "/api/processes/*").as("save");
cy.intercept("POST", "/api/scenarioLabels/validation").as("labelValidation");
cy.get("[data-testid=AddLabel]").should("be.visible").click();
cy.get("[data-testid=LabelInput]").should("be.visible").click().as("labelInput");

labels.forEach((label) => {
cy.get("@labelInput").type(label);
cy.wait("@labelValidation");
cy.get('.MuiAutocomplete-popper li[data-option-index="0"]').contains(label).click();
});

cy.contains(/^save/i).should("be.enabled").click();
cy.contains(/^ok$/i).should("be.enabled").click();
cy.wait("@save").its("response.statusCode").should("eq", 200);
return cy.wrap(processName);
});
}

function deleteTestProcess(processName: string, force?: boolean) {
const url = `/api/processes/${processName}`;

Expand Down Expand Up @@ -168,6 +189,7 @@ function importTestProcess(name: string, fixture = "testProcess") {
cy.request("PUT", `/api/processes/${name}`, {
comment: "import test data",
scenarioGraph: response.scenarioGraph,
scenarioLabels: [],
});
return cy.wrap(name);
});
Expand Down Expand Up @@ -292,6 +314,7 @@ Cypress.Commands.add("createTestFragment", createTestFragment);
Cypress.Commands.add("importTestProcess", importTestProcess);
Cypress.Commands.add("visitNewProcess", visitNewProcess);
Cypress.Commands.add("visitNewFragment", visitNewFragment);
Cypress.Commands.add("addLabelsToNewProcess", addLabelsToNewProcess);
Cypress.Commands.add("postFormData", postFormData);
Cypress.Commands.add("visitProcess", visitProcess);
Cypress.Commands.add("getNode", getNode);
Expand Down
1 change: 1 addition & 0 deletions designer/client/src/actions/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type ActionTypes =
| "RESET_SELECTION"
| "EDIT_NODE"
| "PROCESS_RENAME"
| "EDIT_LABELS"
| "SHOW_METRICS"
| "UPDATE_TEST_CAPABILITIES"
| "UPDATE_TEST_FORM_PARAMETERS"
Expand Down
11 changes: 11 additions & 0 deletions designer/client/src/actions/nk/editNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ export type RenameProcessAction = {
name: string;
};

export type EditScenarioLabels = {
type: "EDIT_LABELS";
labels: string[];
};

export function editScenarioLabels(scenarioLabels: string[]) {
return (dispatch) => {
dispatch({ type: "EDIT_LABELS", labels: scenarioLabels });
};
}

export function editNode(scenarioBefore: Scenario, before: NodeType, after: NodeType, outputEdges?: Edge[]): ThunkAction {
return async (dispatch) => {
const { processName, scenarioGraph } = await dispatch(calculateProcessAfterChange(scenarioBefore, before, after, outputEdges));
Expand Down
5 changes: 3 additions & 2 deletions designer/client/src/actions/nk/node.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Edge, EdgeType, NodeId, NodeType, ProcessDefinitionData, ValidationResult } from "../../types";
import { ThunkAction } from "../reduxTypes";
import { layoutChanged, Position } from "./ui/layout";
import { EditNodeAction, RenameProcessAction } from "./editNode";
import { EditNodeAction, EditScenarioLabels, RenameProcessAction } from "./editNode";
import { getProcessDefinitionData } from "../../reducers/selectors/settings";
import { batchGroupBy } from "../../reducers/graph/batchGroupBy";
import NodeUtils from "../../components/graph/NodeUtils";
Expand Down Expand Up @@ -154,4 +154,5 @@ export type NodeActions =
| NodesWithEdgesAddedAction
| ValidationResultAction
| EditNodeAction
| RenameProcessAction;
| RenameProcessAction
| EditScenarioLabels;
17 changes: 15 additions & 2 deletions designer/client/src/common/ProcessUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import {
import { RootState } from "../reducers";
import { isProcessRenamed } from "../reducers/selectors/graph";
import { Scenario } from "src/components/Process/types";
import { ScenarioLabelValidationError } from "../components/Labels/types";

class ProcessUtils {
nothingToSave = (state: RootState): boolean => {
const scenario = state.graphReducer.scenario;
const savedProcessState = state.graphReducer.history.past[0]?.scenario || state.graphReducer.history.present.scenario;

const omitValidation = (details: ScenarioGraph) => omit(details, ["validationResult"]);
const processRenamed = isProcessRenamed(state);

Expand All @@ -33,7 +33,14 @@ class ProcessUtils {
return true;
}

return !savedProcessState || isEqual(omitValidation(scenario.scenarioGraph), omitValidation(savedProcessState.scenarioGraph));
const labelsFor = (scenario: Scenario): string[] => {
return scenario.labels ? scenario.labels.slice().sort((a, b) => a.localeCompare(b)) : [];
};

const isGraphUpdated = isEqual(omitValidation(scenario.scenarioGraph), omitValidation(savedProcessState.scenarioGraph));
const areScenarioLabelsUpdated = isEqual(labelsFor(scenario), labelsFor(savedProcessState));

return !savedProcessState || (isGraphUpdated && areScenarioLabelsUpdated);
};

canExport = (state: RootState): boolean => {
Expand Down Expand Up @@ -87,6 +94,12 @@ class ProcessUtils {
return isEmpty(this.getValidationErrors(scenario)?.processPropertiesErrors);
};

getLabelsErrors = (scenario: Scenario): ScenarioLabelValidationError[] => {
return this.getValidationResult(scenario)
.errors.globalErrors.filter((e) => e.error.typ == "ScenarioLabelValidationError")
.map((e) => <ScenarioLabelValidationError>{ label: e.error.fieldName, messages: [e.error.description] });
};

getValidationErrors(scenario: Scenario): ValidationErrors {
return this.getValidationResult(scenario).errors;
}
Expand Down
12 changes: 12 additions & 0 deletions designer/client/src/components/Labels/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type AvailableScenarioLabels = {
labels: string[];
};

export type ScenarioLabelValidationError = {
label: string;
messages: string[];
};

export type ScenarioLabelsValidationResponse = {
validationErrors: ScenarioLabelValidationError[];
};
1 change: 1 addition & 0 deletions designer/client/src/components/Process/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface Scenario {
createdAt: Instant;
modifiedAt: Instant;
createdBy: string;
labels: string[];
lastAction?: ProcessActionType;
lastDeployedAction?: ProcessActionType;
state: ProcessStateType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function MoreScenarioDetailsDialog(props: WindowContentProps<WindowKind, Props>)
}, [scenario.processingMode]);

const displayStatus = !scenario.isArchived && !scenario.isFragment;
const displayLabels = scenario.labels.length !== 0;

return (
<WindowContent
Expand Down Expand Up @@ -94,6 +95,12 @@ function MoreScenarioDetailsDialog(props: WindowContentProps<WindowKind, Props>)
<ItemLabelStyled>{i18next.t("scenarioDetails.label.engine", "Engine")}</ItemLabelStyled>
<Typography variant={"caption"}>{scenario.engineSetupName}</Typography>
</ItemWrapperStyled>
{displayLabels && (
<ItemWrapperStyled>
<ItemLabelStyled>{i18next.t("scenarioDetails.label.labels", "Labels")}</ItemLabelStyled>
<Typography variant={"caption"}>{scenario.labels.join(", ")}</Typography>
</ItemWrapperStyled>
)}
<ItemWrapperStyled>
<ItemLabelStyled>{i18next.t("scenarioDetails.label.created", "Created")}</ItemLabelStyled>
<Typography variant={"caption"}>{moment(scenario.createdAt).format(DATE_FORMAT)}</Typography>
Expand Down
5 changes: 3 additions & 2 deletions designer/client/src/components/modals/SaveProcessDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { displayCurrentProcessVersion, displayProcessActivity, loadProcessToolba
import { PromptContent } from "../../windowManager";
import { CommentInput } from "../comment/CommentInput";
import { ThunkAction } from "../../actions/reduxTypes";
import { getScenarioGraph, getProcessName, getProcessUnsavedNewName, isProcessRenamed } from "../../reducers/selectors/graph";
import { getScenarioGraph, getProcessName, getProcessUnsavedNewName, isProcessRenamed, getScenarioLabels } from "../../reducers/selectors/graph";
import HttpService from "../../http/HttpService";
import { ActionCreators as UndoActionCreators } from "redux-undo";
import { visualizationUrl } from "../../common/VisualizationUrl";
Expand All @@ -24,9 +24,10 @@ export function SaveProcessDialog(props: WindowContentProps): JSX.Element {
const state = getState();
const scenarioGraph = getScenarioGraph(state);
const currentProcessName = getProcessName(state);
const labels = getScenarioLabels(state);

// save changes before rename and force same processName everywhere
await HttpService.saveProcess(currentProcessName, scenarioGraph, comment);
await HttpService.saveProcess(currentProcessName, scenarioGraph, comment, labels);

const unsavedNewName = getProcessUnsavedNewName(state);
const isRenamed = isProcessRenamed(state) && (await HttpService.changeProcessName(currentProcessName, unsavedNewName));
Expand Down
4 changes: 2 additions & 2 deletions designer/client/src/components/tips/error/ErrorTips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export const ErrorTips = ({ errors, showDetails, scenario }: Props) => {
() =>
globalErrors.map((error, index) =>
isEmpty(error.nodeIds) ? (
<span key={index} title={error.error.description}>
<div key={index} title={error.error.description}>
{error.error.message}
</span>
</div>
) : (
<NodeErrorsLinkSection
key={index}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ import {
} from "./ScenarioDetailsComponents";
import { MoreScenarioDetailsButton } from "./buttons/MoreScenarioDetailsButton";
import { CategoryDetails } from "./CategoryDetails";
import { ScenarioLabels } from "./ScenarioLabels";
import { getLoggedUser } from "../../../reducers/selectors/settings";

const ScenarioDetails = memo((props: ToolbarPanelProps) => {
const scenario = useSelector((state: RootState) => getScenario(state));
const isRenamePending = useSelector((state: RootState) => isProcessRenamed(state));
const unsavedNewName = useSelector((state: RootState) => getProcessUnsavedNewName(state));
const processState = useSelector((state: RootState) => getProcessState(state));
const loggedUser = useSelector((state: RootState) => getLoggedUser(state));

const transitionKey = ProcessStateUtils.getTransitionKey(scenario, processState);

Expand Down Expand Up @@ -56,6 +59,7 @@ const ScenarioDetails = memo((props: ToolbarPanelProps) => {
<ProcessName variant={"subtitle2"}>{scenario.name}</ProcessName>
)}
</ScenarioDetailsItemWrapper>
<ScenarioLabels readOnly={!loggedUser.isWriter()} />
<MoreScenarioDetailsButton scenario={scenario} processState={processState} />
</PanelScenarioDetails>
</CssFade>
Expand Down
Loading
Loading