Skip to content

Commit

Permalink
chore: Added UI changes for enable and disable cron feature (#4286)
Browse files Browse the repository at this point in the history
* chore: Added UI changes for enable and disable cron feature

Signed-off-by: Amit Kumar Das <[email protected]>

* added util function to validate cron and minor fixes

Signed-off-by: Amit Kumar Das <[email protected]>

* minor fix

Signed-off-by: Amit Kumar Das <[email protected]>

* fixed import order

Signed-off-by: Amit Kumar Das <[email protected]>

* fixed linting issues

Signed-off-by: Amit Kumar Das <[email protected]>

* fixed linting issues

Signed-off-by: Amit Kumar Das <[email protected]>

---------

Signed-off-by: Amit Kumar Das <[email protected]>
Co-authored-by: Saranya Jena <[email protected]>
  • Loading branch information
amityt and Saranya-jena authored Nov 15, 2023
1 parent 2c5557a commit 0390cdf
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 10 deletions.
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,10 @@ 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?.experimentType === ExperimentType.CRON && cronEnabled(parsedManifest);
setIsCronEnabled(validateCron);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [experimentData, experimentID, hasUnsavedChangesInURL]);
Expand All @@ -78,7 +85,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,9 +1,8 @@
import { useToaster } from '@harnessio/uicore';
import React from 'react';
import { useParams } from 'react-router-dom';
import type { ExecutionData } from '@api/entities';
import { ExperimentRunStatus } from '@api/entities';
import { getScope } from '@utils';
import { ExecutionData, ExperimentType, ExperimentRunStatus } from '@api/entities';
import { cronEnabled, getScope } from '@utils';
import ExperimentRunDetailsView from '@views/ExperimentRunDetails';
import RightSideBarV2 from '@components/RightSideBarV2';
import { getExperimentRun } from '@api/core/experiments/getExperimentRun';
Expand Down Expand Up @@ -46,11 +45,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 Down Expand Up @@ -119,6 +119,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 +150,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

0 comments on commit 0390cdf

Please sign in to comment.