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

chore: Added UI changes for enable and disable cron feature #4286

Merged
merged 7 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions chaoscenter/web/src/api/core/experiments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './updateChaosWorkflow';
export * from './stopWorkflow';
export * from './deleteChaosWorkflow';
export * from './saveChaosExperiment';
export * from './updateCronExperimentState';
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export function listExperimentRunForHistory({
updatedBy {
username
}
experimentManifest
updatedAt
resiliencyScore
phase
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { gql, useMutation } from '@apollo/client';
import type { GqlAPIMutationRequest, GqlAPIMutationResponse } from '@api/types';

export interface UpdateCronExperimentStateRequest {
disable: boolean;
projectID: string;
experimentID?: string;
}

export interface UpdateCronExperimentStateResponse {
updateCronExperimentState: boolean;
}

export function useUpdateCronExperimentStateMutation(
options?: GqlAPIMutationRequest<UpdateCronExperimentStateResponse, UpdateCronExperimentStateRequest>
): GqlAPIMutationResponse<UpdateCronExperimentStateResponse, UpdateCronExperimentStateRequest> {
const [updateCronExperimentStateMutation, result] = useMutation<
UpdateCronExperimentStateResponse,
UpdateCronExperimentStateRequest
>(
gql`
mutation updateCronExperimentState($experimentID: String!, $disable: Boolean!, $projectID: ID!) {
updateCronExperimentState(experimentID: $experimentID, disable: $disable, projectID: $projectID)
}
`,
options
);

return [updateCronExperimentStateMutation, result];
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import { Intent, Position } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { parse } from 'yaml';
import { useStrings } from '@strings';
import { listExperiment, runChaosExperiment, stopExperiment, stopExperimentRun } from '@api/core';
import {
listExperiment,
runChaosExperiment,
stopExperiment,
stopExperimentRun,
useUpdateCronExperimentStateMutation
} from '@api/core';
import { useRouteWithBaseUrl } from '@hooks';
import type { RefetchExperimentRuns, RefetchExperiments } from '@controllers/ExperimentDashboardV2';
import { PermissionGroup, StudioTabs } from '@models';
Expand Down Expand Up @@ -351,3 +357,78 @@ export const DownloadExperimentButton = ({
</div>
);
};

interface EnableDisableCronButtonProps extends ActionButtonProps, Partial<RefetchExperiments> {
isCronEnabled: boolean;
}

export const EnableDisableCronButton = ({
experimentID,
tooltipProps,
isCronEnabled,
refetchExperiments
}: EnableDisableCronButtonProps): React.ReactElement => {
const scope = getScope();
const { getString } = useStrings();
const { showSuccess, showError } = useToaster();
const {
isOpen: isOpenCronEnableDisableDialog,
open: openCronEnableDisableDialog,
close: closeCronEnableDisableDialog
} = useToggleOpen();

const [updateCronExperimentStateMutation] = useUpdateCronExperimentStateMutation({
onCompleted: () => {
showSuccess(isCronEnabled ? getString('cronHalted') : getString('cronResumed'));
refetchExperiments?.();
},
onError: err => showError(err.message)
});

const cronEnableDisableDialogProps: ConfirmationDialogProps = {
isOpen: isOpenCronEnableDisableDialog,
contentText: isCronEnabled ? getString('disableCronDesc') : getString('enableCronDesc'),
titleText: isCronEnabled ? `${getString('disableCron')}?` : `${getString('enableCron')}?`,
cancelButtonText: getString('cancel'),
confirmButtonText: getString('confirm'),
intent: Intent.WARNING,
onClose: (isConfirmed: boolean) => {
if (isConfirmed) {
updateCronExperimentStateMutation({
variables: {
projectID: scope.projectID,
experimentID: experimentID,
disable: isCronEnabled ? true : false
}
});
}
closeCronEnableDisableDialog();
}
};

const cronEnableDisableDialog = <ConfirmationDialog {...cronEnableDisableDialogProps} />;

return (
<div className={cx(css.actionButtons, css.withBg)}>
<div>
<RbacButton
tooltip={isCronEnabled ? getString('disableCron') : getString('enableCron')}
iconProps={{ size: 18 }}
withoutCurrentColor
intent={isCronEnabled ? 'danger' : 'success'}
tooltipProps={{
position: Position.TOP,
usePortal: true,
isDark: true,
...tooltipProps
}}
variation={ButtonVariation.ICON}
icon={'time'}
onClick={openCronEnableDisableDialog}
permission={PermissionGroup.EDITOR}
/>
</div>
{cronEnableDisableDialog}
</div>
);
};
29 changes: 27 additions & 2 deletions chaoscenter/web/src/components/RightSideBarV2/RightSideBarV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CloneExperimentButton,
DownloadExperimentButton,
EditExperimentButton,
EnableDisableCronButton,
RunExperimentButton,
StopExperimentButton,
StopExperimentRunButton
Expand All @@ -21,6 +22,7 @@ interface RightSideBarViewV2Props extends Partial<RefetchExperiments>, Partial<R
experimentType?: ExperimentType;
phase: ExperimentRunStatus | undefined;
loading?: boolean;
isCronEnabled?: boolean;
isEditMode?: boolean;
}

Expand All @@ -32,14 +34,15 @@ function RightSideBarV2({
phase,
loading,
isEditMode,
isCronEnabled,
refetchExperiments,
refetchExperimentRuns
}: RightSideBarViewV2Props): React.ReactElement {
const { getString } = useStrings();

const showStopButton = phase === ExperimentRunStatus.RUNNING || phase === ExperimentRunStatus.QUEUED;

const showEnableDisableCronButton = experimentType === ExperimentType.CRON;
const showEnableDisableCronButton =
experimentType && experimentType === ExperimentType.CRON && isCronEnabled !== undefined;

return (
<Layout.Vertical
Expand All @@ -49,6 +52,28 @@ function RightSideBarV2({
spacing={'xlarge'}
className={loading ? Classes.SKELETON : ''}
>
{showEnableDisableCronButton && (
// <!-- enable/disable button for cron experiments -->
<Container>
<Layout.Vertical flex={{ justifyContent: 'center' }} spacing={'small'}>
<EnableDisableCronButton
tooltipProps={{ disabled: true }}
experimentID={experimentID}
refetchExperiments={refetchExperiments}
isCronEnabled={isCronEnabled}
/>
<Text
style={{ textAlign: 'center' }}
width={40}
color={Color.GREY_500}
font={{ variation: FontVariation.TINY_SEMI }}
>
{isCronEnabled ? getString('disableCron') : getString('enableCron')}
</Text>
</Layout.Vertical>
</Container>
)}

{showStopButton ? (
experimentRunID || notifyID ? (
// <!-- stop button for experiment run (specific run details page) -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ import { getScope } from '@utils';
import ChaosStudioView from '@views/ChaosStudio';
import { listExperiment, runChaosExperiment, saveChaosExperiment } from '@api/core';
import experimentYamlService from '@services/experiment';
import { InfrastructureType, RecentExperimentRun } from '@api/entities';
import { ExperimentType, InfrastructureType, RecentExperimentRun } from '@api/entities';
import Loader from '@components/Loader';
import { useSearchParams, useUpdateSearchParams } from '@hooks';
import RightSideBarV2 from '@components/RightSideBarV2';
import { StudioMode } from '@models';
import { cronEnabled } from 'utils';

export default function ChaosStudioEditController(): React.ReactElement {
const scope = getScope();
const { showError } = useToaster();
const searchParams = useSearchParams();
const updateSearchParams = useUpdateSearchParams();
const hasUnsavedChangesInURL = searchParams.get('unsavedChanges') === 'true';
const experimentType = searchParams.get('experimentType');

// <!-- counting state since we have 2 async functions and need to flip state when both of said functions have resolved their promises -->
const [showStudio, setShowStudio] = React.useState<number>(0);
Expand All @@ -39,6 +41,7 @@ export default function ChaosStudioEditController(): React.ReactElement {
)[0];

const [lastExperimentRun, setLastExperimentRun] = React.useState<RecentExperimentRun | undefined>();
const [isCronEnabled, setIsCronEnabled] = React.useState<boolean>();

React.useEffect(() => {
if (experimentData && showStudio < 2 && !hasUnsavedChangesInURL) {
Expand All @@ -65,6 +68,11 @@ export default function ChaosStudioEditController(): React.ReactElement {
?.updateExperimentManifest(experimentID, parse(experimentData.experimentManifest))
.then(() => setShowStudio(oldState => oldState + 1));
setLastExperimentRun(experimentData.recentExperimentRunDetails?.[0]);

const parsedManifest = JSON.parse(experimentData.experimentManifest);
const validateCron =
experimentData && experimentData?.experimentType === ExperimentType.CRON && cronEnabled(parsedManifest);
setIsCronEnabled(validateCron);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [experimentData, experimentID, hasUnsavedChangesInURL]);
Expand All @@ -78,7 +86,15 @@ export default function ChaosStudioEditController(): React.ReactElement {
onCompleted: () => listExperimentRefetch()
});

const rightSideBarV2 = <RightSideBarV2 experimentID={experimentID} isEditMode phase={lastExperimentRun?.phase} />;
const rightSideBarV2 = (
<RightSideBarV2
experimentID={experimentID}
isCronEnabled={isCronEnabled}
isEditMode
phase={lastExperimentRun?.phase}
experimentType={experimentType as ExperimentType}
/>
);

return (
<Loader loading={showStudio < 2 && !hasUnsavedChangesInURL} height="var(--page-min-height)">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useToaster } from '@harnessio/uicore';
import React from 'react';
import { useParams } from 'react-router-dom';
import type { ExecutionData } from '@api/entities';
import { ExecutionData, ExperimentType } from '@api/entities';
import { ExperimentRunStatus } from '@api/entities';
import { getScope } from '@utils';
import { cronEnabled, getScope } from '@utils';
import ExperimentRunDetailsView from '@views/ExperimentRunDetails';
import RightSideBarV2 from '@components/RightSideBarV2';
import { getExperimentRun } from '@api/core/experiments/getExperimentRun';
import type { CronWorkflow, Workflow } from '@models';

export default function ExperimentRunDetailsController(): React.ReactElement {
const { experimentID, runID, notifyID } = useParams<{ experimentID: string; runID: string; notifyID: string }>();
Expand Down Expand Up @@ -46,11 +47,17 @@ export default function ExperimentRunDetailsController(): React.ReactElement {
? (JSON.parse(specificRunData.executionData) as ExecutionData)
: undefined;

const parsedManifest =
specificRunData && specificRunData?.experimentManifest ? JSON.parse(specificRunData.experimentManifest) : undefined;
const isCronEnabled =
specificRunExists && specificRunData?.experimentType === ExperimentType.CRON && cronEnabled(parsedManifest);

const rightSideBarV2 = (
<RightSideBarV2
experimentID={experimentID}
experimentRunID={runID}
notifyID={notifyID}
isCronEnabled={isCronEnabled}
phase={specificRunData?.phase}
experimentType={specificRunData?.experimentType}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { useParams } from 'react-router-dom';
import { Color } from '@harnessio/design-system';
import { isEqual } from 'lodash-es';
import { listExperimentRunForHistory } from '@api/core';
import { getScope, getColorBasedOnResilienceScore } from '@utils';
import { getScope, getColorBasedOnResilienceScore, cronEnabled } from '@utils';
import ExperimentRunHistoryView from '@views/ExperimentRunHistory';
import { useStrings } from '@strings';
import type { ExperimentRun } from '@api/entities';
import { ExperimentRun, ExperimentType } from '@api/entities';
import type { ColumnData } from '@components/ColumnChart/ColumnChart.types';
import {
initialExperimentRunFilterState,
Expand All @@ -27,6 +27,7 @@ import {
} from './ExperimentRunFilter';
import type { ExperimentRunHistoryTableProps } from './types';
import { generateExperimentRunTableContent } from './helpers';
import type { CronWorkflow } from '@models';

const Tooltip = ({ experimentRun }: { experimentRun: ExperimentRun }): React.ReactElement => {
const { getString } = useStrings();
Expand Down Expand Up @@ -119,6 +120,7 @@ export default function ExperimentRunHistoryController(): React.ReactElement {
const experimentName = experimentRunsWithExecutionData?.[0]?.experimentName;
const experimentPhase = experimentRunsWithExecutionData?.[0]?.phase;
const experimentType = experimentRunsWithExecutionData?.[0]?.experimentType;
const experimentManifest = experimentRunsWithExecutionData?.[0]?.experimentManifest;

React.useEffect(() => {
if (experimentName) setExperimentNamePersistent(experimentName);
Expand Down Expand Up @@ -149,11 +151,17 @@ export default function ExperimentRunHistoryController(): React.ReactElement {

const areFiltersSet = !(isEqual(state, initialExperimentRunFilterState) && page === 0);

const parsedManifest = experimentManifest && JSON.parse(experimentManifest);

const isCronEnabled =
experimentRunsWithExecutionData && experimentType === ExperimentType.CRON && cronEnabled(parsedManifest);

const rightSideBarV2 = (
<RightSideBarV2
refetchExperimentRuns={refetchExperimentRuns}
experimentID={experimentID}
phase={experimentPhase}
isCronEnabled={isCronEnabled}
experimentType={experimentType}
/>
);
Expand Down
9 changes: 9 additions & 0 deletions chaoscenter/web/src/strings/strings.en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,15 @@ createdOn: Created On
creationTime: Creation Time
criteria: Criteria
criteriaForData: Criteria for data
cron: Cron
cronDisabled: Cron Schedule is disabled
cronHalted: This cron experiment as been halted successfully.
cronResumed: This cron experiment has re-scheduled successfully.
disableCron: Disable Cron
disableCronDesc: This will disable the cron schedule for this experiment.
enableCron: Enable Cron
enableCronDesc: This will enable the cron schedule for this experiment.
nonCron: Non-Cron
cronExpression: Cron Expression
cronExpressionRequired: Cron Expression required
cronSelectOption: Cron (Recurring run)
Expand Down
9 changes: 9 additions & 0 deletions chaoscenter/web/src/strings/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,12 @@ export interface StringsMap {
'creationTime': unknown
'criteria': unknown
'criteriaForData': unknown
'cron': unknown
'cronDisabled': unknown
'cronExpression': unknown
'cronExpressionRequired': unknown
'cronHalted': unknown
'cronResumed': unknown
'cronSelectOption': unknown
'cronText': unknown
'currentRun': unknown
Expand Down Expand Up @@ -219,6 +223,8 @@ export interface StringsMap {
'detailsAndProperties': unknown
'disable': unknown
'disableChaosInfrastructure': unknown
'disableCron': unknown
'disableCronDesc': unknown
'disableUser': unknown
'disableUserDescription': unknown
'discard': unknown
Expand Down Expand Up @@ -260,6 +266,8 @@ export interface StringsMap {
'enableChaosInfraButton': unknown
'enableChaosInfrastructure': unknown
'enableChaosInfrastructureDesc': unknown
'enableCron': unknown
'enableCronDesc': unknown
'enableImageRegistryChanges': unknown
'enableSSLCheck': unknown
'enableUser': unknown
Expand Down Expand Up @@ -569,6 +577,7 @@ export interface StringsMap {
'nodeSelectorPlaceholderForKey': unknown
'nodeSelectorPlaceholderForValue': unknown
'nodeSelectorText': unknown
'nonCron': unknown
'nonCronSelectOption': unknown
'nonCronText': unknown
'nonProd': unknown
Expand Down
Loading