Skip to content

Commit e8062b6

Browse files
authored
Merge branch 'master' into replace-strings-with-translations
2 parents d02908b + ff71c78 commit e8062b6

File tree

14 files changed

+247
-23
lines changed

14 files changed

+247
-23
lines changed

chaoscenter/graphql/server/pkg/chaos_infrastructure/infra_utils.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ type SubscriberConfigurations struct {
2020
}
2121

2222
func GetEndpoint(host string) (string, error) {
23+
const apiPath = "/api/query"
2324
// returns endpoint from env, if provided by user
2425
if utils.Config.ChaosCenterUiEndpoint != "" {
25-
return utils.Config.ChaosCenterUiEndpoint + "/api/query", nil
26+
return strings.TrimRight(utils.Config.ChaosCenterUiEndpoint, "/") + apiPath, nil
2627
}
2728

28-
return host + "/api/query", nil
29+
return strings.TrimRight(host, "/") + apiPath, nil
2930
}
3031

3132
func GetK8sInfraYaml(host string, infra dbChaosInfra.ChaosInfra) ([]byte, error) {

chaoscenter/graphql/server/pkg/chaos_infrastructure/service.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -954,9 +954,16 @@ func (in *infraService) QueryServerVersion(ctx context.Context) (*model.ServerVe
954954
if err != nil {
955955
return nil, err
956956
}
957+
if dbVersion == nil {
958+
return nil, errors.New("server version config not found")
959+
}
960+
versionStr, ok := dbVersion.Value.(string)
961+
if !ok {
962+
return nil, errors.New("server version config has unexpected type")
963+
}
957964
return &model.ServerVersionResponse{
958965
Key: dbVersion.Key,
959-
Value: dbVersion.Value.(string),
966+
Value: versionStr,
960967
}, nil
961968
}
962969

chaoscenter/web/src/components/ExperimentActionButtons/ExperimentActionButtons.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,11 @@ export const RunExperimentButton = ({
5656
const { showSuccess, showError } = useToaster();
5757
const paths = useRouteWithBaseUrl();
5858
const history = useHistory();
59-
const [runChaosExperimentMutation] = runChaosExperiment({
59+
const runInFlightRef = React.useRef(false);
60+
61+
const [runChaosExperimentMutation, { loading: runChaosExperimentLoading }] = runChaosExperiment({
6062
onCompleted: response => {
63+
runInFlightRef.current = false;
6164
showSuccess(getString('reRunSuccessful'));
6265
refetchExperiments?.();
6366
const notifyID = response.runChaosExperiment.notifyID;
@@ -71,6 +74,7 @@ export const RunExperimentButton = ({
7174
}
7275
},
7376
onError: err => {
77+
runInFlightRef.current = false;
7478
showError(err.message);
7579
}
7680
});
@@ -84,13 +88,15 @@ export const RunExperimentButton = ({
8488
isDark: true,
8589
...tooltipProps
8690
}}
87-
disabled={disabled}
91+
disabled={disabled || runChaosExperimentLoading}
8892
iconProps={{ size: 18 }}
8993
icon={'play'}
9094
withoutCurrentColor
9195
variation={ButtonVariation.ICON}
9296
permission={PermissionGroup.Executor}
9397
onClick={() => {
98+
if (disabled || runChaosExperimentLoading || runInFlightRef.current) return;
99+
runInFlightRef.current = true;
94100
runChaosExperimentMutation({
95101
variables: { projectID: scope.projectID, experimentID: experimentID }
96102
});

chaoscenter/web/src/components/LitmusBreadCrumbs/LitmusBreadCrumbs.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
import { Breadcrumbs, BreadcrumbsProps } from '@harnessio/uicore';
22
import React from 'react';
3+
import { useParams } from 'react-router-dom';
34
import { useAppStore } from '@context';
45

56
interface LitmusBreadCrumbsProps extends BreadcrumbsProps {
67
baseUrl?: string;
78
}
89

910
export default function LitmusBreadCrumbs({ baseUrl, ...rest }: LitmusBreadCrumbsProps): React.ReactElement {
10-
const { projectName } = useAppStore();
11+
const { projectName, projectID } = useAppStore();
12+
const { accountID } = useParams<{ accountID: string }>();
13+
14+
const projectUrl =
15+
projectID && projectID.trim() !== ''
16+
? `/account/${accountID}/project/${projectID}/dashboard`
17+
: `/account/${accountID}/settings/projects`;
1118

1219
const combinedLinks = [
1320
{
1421
label: projectName || 'My Project',
15-
url: '/',
22+
url: projectUrl,
1623
iconProps: { name: 'chaos-litmuschaos' as any } // cast to 'any' to avoid TS type issue
1724
},
1825
...(rest.links || [])

chaoscenter/web/src/components/ProjectCard/ProjectCard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { AvatarGroup, Card, Container, Text } from '@harnessio/uicore';
33
import { FontVariation, Color } from '@harnessio/design-system';
4-
import { useHistory } from 'react-router-dom';
4+
import { useHistory, useParams } from 'react-router-dom';
55
import { useAppStore } from '@context';
66
import { useStrings } from '@strings';
77
import type { Project } from '@api/auth';
@@ -15,6 +15,7 @@ interface ProjectCardProps {
1515
export default function ProjectCard({ data }: ProjectCardProps): React.ReactElement {
1616
const { getString } = useStrings();
1717
const history = useHistory();
18+
const { accountID } = useParams<{ accountID: string }>();
1819
const { projectID, currentUserInfo, updateAppStore } = useAppStore();
1920

2021
const isSelected = projectID === data.projectID;
@@ -32,7 +33,7 @@ export default function ProjectCard({ data }: ProjectCardProps): React.ReactElem
3233
projectRole,
3334
projectID: data.projectID
3435
});
35-
history.replace(`/`);
36+
history.replace(`/account/${accountID}/project/${data.projectID}/dashboard`);
3637
};
3738

3839
return (

chaoscenter/web/src/components/ProjectDashboardCardContainer/ProjectDashboardCardContainer.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React, { useState } from 'react';
33
import { Color, FontVariation } from '@harnessio/design-system';
44
import { Classes, PopoverInteractionKind, Position, Menu, Dialog } from '@blueprintjs/core';
55
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
6-
import { useHistory } from 'react-router-dom';
6+
import { useHistory, useParams } from 'react-router-dom';
77
import { ListProjectsOkResponse, Project } from '@api/auth';
88
import CustomTagsPopover from '@components/CustomTagsPopover';
99
import { useStrings } from '@strings';
@@ -24,6 +24,7 @@ export default function ProjectDashboardCardContainer(props: ProjectDashboardCar
2424
const [projectIdToDelete, setProjectIdToDelete] = useState<string>();
2525
const { getString } = useStrings();
2626
const history = useHistory();
27+
const { accountID } = useParams<{ accountID: string }>();
2728
const { updateAppStore, currentUserInfo } = useAppStore();
2829

2930
const handleProjectSelect = (project: Project): void => {
@@ -33,7 +34,7 @@ export default function ProjectDashboardCardContainer(props: ProjectDashboardCar
3334
projectRole,
3435
projectID: project.projectID
3536
});
36-
history.replace(`/`);
37+
history.replace(`/account/${accountID}/project/${project.projectID}/dashboard`);
3738
};
3839

3940
return (

chaoscenter/web/src/components/RightSideBarV2/RightSideBarV2.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ interface RightSideBarViewV2Props extends Partial<RefetchExperiments>, Partial<R
2828
isCronEnabled?: boolean;
2929
setIsCronEnabled?: React.Dispatch<React.SetStateAction<boolean | undefined>>;
3030
isEditMode?: boolean;
31+
isExperimentRunning?: boolean;
32+
runDisabled?: boolean;
3133
}
3234

3335
function RightSideBarV2({
@@ -40,6 +42,8 @@ function RightSideBarV2({
4042
isEditMode,
4143
isCronEnabled,
4244
setIsCronEnabled,
45+
isExperimentRunning,
46+
runDisabled,
4347
refetchExperiments,
4448
refetchExperimentRuns
4549
}: RightSideBarViewV2Props): React.ReactElement {
@@ -70,7 +74,9 @@ function RightSideBarV2({
7074
}, [experimentList]);
7175

7276
const { getString } = useStrings();
73-
const showStopButton = phase === ExperimentRunStatus.RUNNING || phase === ExperimentRunStatus.QUEUED;
77+
const showStopButton = isEditMode
78+
? isExperimentRunning // In edit mode, only rely on isExperimentRunning prop
79+
: isExperimentRunning || phase === ExperimentRunStatus.RUNNING || phase === ExperimentRunStatus.QUEUED;
7480

7581
const showEnableDisableCronButton =
7682
experimentType && experimentType === ExperimentType.CRON && isCronEnabled !== undefined;
@@ -158,7 +164,7 @@ function RightSideBarV2({
158164
tooltipProps={{ disabled: true }}
159165
experimentID={experimentID}
160166
refetchExperiments={refetchExperiments}
161-
// buttonProps={{ disabled: phase === ExperimentRunStatus.QUEUED }}
167+
disabled={Boolean(runDisabled)}
162168
/>
163169
<Text
164170
style={{ textAlign: 'center' }}

chaoscenter/web/src/controllers/ChaosStudioEdit/ChaosStudioEdit.tsx

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { getScope } from '@utils';
66
import ChaosStudioView from '@views/ChaosStudio';
77
import { listExperiment, runChaosExperiment, saveChaosExperiment } from '@api/core';
88
import experimentYamlService from '@services/experiment';
9-
import { ExperimentType, InfrastructureType, RecentExperimentRun } from '@api/entities';
9+
import { ExperimentRunStatus, ExperimentType, InfrastructureType, RecentExperimentRun } from '@api/entities';
1010
import Loader from '@components/Loader';
1111
import { useSearchParams, useUpdateSearchParams } from '@hooks';
1212
import RightSideBarV2 from '@components/RightSideBarV2';
@@ -31,8 +31,7 @@ export default function ChaosStudioEditController(): React.ReactElement {
3131
experimentIDs: [experimentID],
3232
options: {
3333
onError: err => showError(err.message),
34-
fetchPolicy: 'cache-first',
35-
skip: showStudio >= 2 || hasUnsavedChangesInURL
34+
fetchPolicy: 'cache-and-network'
3635
}
3736
});
3837

@@ -43,6 +42,31 @@ export default function ChaosStudioEditController(): React.ReactElement {
4342
const [lastExperimentRun, setLastExperimentRun] = React.useState<RecentExperimentRun | undefined>();
4443
const [isCronEnabled, setIsCronEnabled] = React.useState<boolean>();
4544

45+
const runInProgressStorageKey = `litmus:runInProgress:${scope.projectID}:${experimentID}`;
46+
const [runInProgressFromStorage, setRunInProgressFromStorage] = React.useState<boolean>(() => {
47+
try {
48+
return typeof window !== 'undefined' && window.sessionStorage.getItem(runInProgressStorageKey) === 'true';
49+
} catch (e) {
50+
return false;
51+
}
52+
});
53+
54+
const isExperimentCurrentlyRunning =
55+
runInProgressFromStorage ||
56+
lastExperimentRun?.phase === ExperimentRunStatus.RUNNING ||
57+
lastExperimentRun?.phase === ExperimentRunStatus.QUEUED;
58+
59+
// Poll experiment status while a run is active
60+
React.useEffect(() => {
61+
if (!isExperimentCurrentlyRunning) return;
62+
63+
const pollInterval = setInterval(() => {
64+
listExperimentRefetch();
65+
}, 3000); // Poll every 3 seconds
66+
67+
return () => clearInterval(pollInterval);
68+
}, [isExperimentCurrentlyRunning, listExperimentRefetch]);
69+
4670
React.useEffect(() => {
4771
if (experimentData && showStudio < 2 && !hasUnsavedChangesInURL) {
4872
const infrastructureType = InfrastructureType.KUBERNETES;
@@ -67,8 +91,28 @@ export default function ChaosStudioEditController(): React.ReactElement {
6791
experimentHandler
6892
?.updateExperimentManifest(experimentID, parse(experimentData.experimentManifest))
6993
.then(() => setShowStudio(oldState => oldState + 1));
70-
setLastExperimentRun(experimentData.recentExperimentRunDetails?.[0]);
7194
}
95+
96+
// Always keep run status in sync (even when there are unsaved editor changes)
97+
if (experimentData) {
98+
const latestRun = experimentData.recentExperimentRunDetails?.[0];
99+
setLastExperimentRun(latestRun);
100+
101+
const isActiveRun =
102+
latestRun?.phase === ExperimentRunStatus.RUNNING || latestRun?.phase === ExperimentRunStatus.QUEUED;
103+
104+
try {
105+
if (typeof window !== 'undefined') {
106+
if (isActiveRun) window.sessionStorage.setItem(runInProgressStorageKey, 'true');
107+
else window.sessionStorage.removeItem(runInProgressStorageKey);
108+
}
109+
} catch (e) {
110+
// ignore storage failures
111+
}
112+
113+
setRunInProgressFromStorage(isActiveRun);
114+
}
115+
72116
if (experimentData && showStudio < 2) {
73117
const parsedManifest = JSON.parse(experimentData.experimentManifest);
74118
const validateCron = experimentData?.experimentType === ExperimentType.CRON && cronEnabled(parsedManifest);
@@ -86,6 +130,8 @@ export default function ChaosStudioEditController(): React.ReactElement {
86130
onCompleted: () => listExperimentRefetch()
87131
});
88132

133+
const isExperimentRunning = isExperimentCurrentlyRunning;
134+
89135
const rightSideBarV2 = (
90136
<RightSideBarV2
91137
experimentID={experimentID}
@@ -94,6 +140,8 @@ export default function ChaosStudioEditController(): React.ReactElement {
94140
isEditMode
95141
phase={lastExperimentRun?.phase}
96142
experimentType={experimentType as ExperimentType}
143+
isExperimentRunning={isExperimentRunning}
144+
runDisabled={hasUnsavedChangesInURL || saveChaosExperimentLoading || runChaosExperimentLoading}
97145
/>
98146
);
99147

@@ -107,6 +155,7 @@ export default function ChaosStudioEditController(): React.ReactElement {
107155
runChaosExperiment: runChaosExperimentLoading
108156
}}
109157
mode={StudioMode.EDIT}
158+
isExperimentRunning={isExperimentRunning}
110159
rightSideBar={rightSideBarV2}
111160
allowSwitchToRunHistory={lastExperimentRun !== undefined}
112161
/>

chaoscenter/web/src/controllers/Login/LoginPage.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ const LoginController: React.FC = () => {
3030
projectID: dexProjectID,
3131
projectRole: dexProjectRole ?? ''
3232
});
33-
history.push(normalizePath(`/account/${accountID}/project/${dexProjectID ?? ''}/dashboard`));
33+
if (dexProjectID && dexProjectID.trim() !== '') {
34+
history.push(normalizePath(`/account/${accountID}/project/${dexProjectID}/dashboard`));
35+
} else {
36+
history.push(normalizePath(`/account/${accountID}/settings/projects`));
37+
}
3438
}
3539
}, []);
3640

@@ -70,10 +74,10 @@ const LoginController: React.FC = () => {
7074
});
7175
if (response.isInitialLogin) {
7276
history.push(`/account/${userDetails.accountID}/settings/password-reset`);
77+
} else if (userDetails.projectID && userDetails.projectID.trim() !== '') {
78+
history.push(normalizePath(`/account/${userDetails.accountID}/project/${userDetails.projectID}/dashboard`));
7379
} else {
74-
history.push(
75-
normalizePath(`/account/${userDetails.accountID}/project/${userDetails.projectID ?? ''}/dashboard`)
76-
);
80+
history.push(normalizePath(`/account/${userDetails.accountID}/settings/projects`));
7781
}
7882
}
7983
}

chaoscenter/web/src/hooks/useRouteWithBaseUrl.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export function useRouteWithBaseUrl(scope?: RouteScope): UseRouteDefinitionsProp
1818
const { renderUrl, projectID } = useAppStore();
1919

2020
function withProjectID(route: string): string {
21+
if (!projectID || projectID.trim() === '') {
22+
return `/settings/projects`;
23+
}
2124
return `/project/${projectID}/${route.replace(/^\//, '')}`;
2225
}
2326

0 commit comments

Comments
 (0)