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

feat(diagnostics): Add Dashboard card for invoking diagnostic operations #1426

Merged
merged 23 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d9ac002
Initial implementation
Josh-Matsuoka Sep 24, 2024
db065a4
Fix incorrect API call
Josh-Matsuoka Sep 24, 2024
36576c1
Fix API path
Josh-Matsuoka Sep 24, 2024
e8d9713
Fix API request
Josh-Matsuoka Sep 24, 2024
5c3a96a
Run Prettier
Josh-Matsuoka Sep 24, 2024
d934f92
Merge branch 'main' into diagnostics-card-1
Josh-Matsuoka Sep 24, 2024
ec57f58
Fix error handling
Josh-Matsuoka Sep 25, 2024
5ad55e9
Add loading spinner to diagnostic card
Josh-Matsuoka Sep 27, 2024
ad18b1b
Move DiagnosticsCard, clean up localization
Josh-Matsuoka Oct 1, 2024
d0374c6
Merge branch 'main' into diagnostics-card-1
Josh-Matsuoka Oct 1, 2024
4de2d58
Merge remote-tracking branch 'upstream/main' into diagnostics-card-1
andrewazores Oct 1, 2024
c1628d0
Remove refresh button, cleanup
Josh-Matsuoka Oct 1, 2024
115fd63
Merge remote-tracking branch 'jmatsuok/diagnostics-card-1' into diagn…
Josh-Matsuoka Oct 1, 2024
f2913dc
remove unused translation component
andrewazores Oct 1, 2024
0facbbf
remove dead code
andrewazores Oct 1, 2024
30351c9
restore default card header actions
andrewazores Oct 1, 2024
c9e3099
inline props, remove unused props
andrewazores Oct 1, 2024
d8aceef
remove unused title prop
andrewazores Oct 1, 2024
ca8fde9
ensure observables complete after first emission
andrewazores Oct 1, 2024
4bfdef2
loading state cleanup
andrewazores Oct 1, 2024
420a52b
pass diagnostics failure back to card for handling
andrewazores Oct 1, 2024
4f5896d
error notification styling
andrewazores Oct 1, 2024
76a3344
lint
andrewazores Oct 1, 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
6 changes: 6 additions & 0 deletions locales/en/public.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@
"CONTENT": "Severity scores are calculated based on the number of JFR events that were triggered by the application in the time the report was generated."
}
},
"DiagnosticsCard": {
"DIAGNOSTICS_CARD_DESCRIPTION": "Perform diagnostic operations on the target.",
"DIAGNOSTICS_CARD_DESCRIPTION_FULL": "Perform diagonstic operations from a list of supported operations on the target.",
"DIAGNOSTICS_CARD_TITLE": "Diagnostics",
"DIAGNOSTICS_GC_BUTTON": "Start Garbage Collection"
},
"CHART_CARD": {
"BUTTONS": {
"CREATE": {
Expand Down
204 changes: 204 additions & 0 deletions src/app/Dashboard/Diagnostics/DiagnosticsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* Copyright The Cryostat Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
DashboardCardTypeProps,
DashboardCardFC,
DashboardCardSizes,
DashboardCardDescriptor,
} from '@app/Dashboard/types';
import { ErrorView } from '@app/ErrorView/ErrorView';
import { FeatureLevel } from '@app/Shared/Services/service.types';
import { LoadingProps } from '@app/Shared/Components/types';

Check warning on line 25 in src/app/Dashboard/Diagnostics/DiagnosticsCard.tsx

View workflow job for this annotation

GitHub Actions / eslint-check (lts/*)

`@app/Shared/Components/types` import should occur before import of `@app/Shared/Services/service.types`
Josh-Matsuoka marked this conversation as resolved.
Show resolved Hide resolved
import { ServiceContext } from '@app/Shared/Services/Services';
import { useSubscriptions } from '@app/utils/hooks/useSubscriptions';
import {
Bullseye,
Button,
CardBody,
CardHeader,
CardTitle,
EmptyState,
EmptyStateBody,
EmptyStateIcon,
EmptyStateVariant,
Label,
EmptyStateHeader,
EmptyStateFooter,
} from '@patternfly/react-core';
import { DataSourceIcon, SyncAltIcon, TachometerAltIcon } from '@patternfly/react-icons';
import * as React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { DashboardCard } from '../DashboardCard';

export interface DiagnosticsCardProps extends DashboardCardTypeProps {
chartKind: string;
duration: number;
period: number;
}

export enum DiagnosticsCardKind {}

export function kindToId(kind: string): number {
return DiagnosticsCardKind[kind];
}

export const DiagnosticsCard: DashboardCardFC<DiagnosticsCardProps> = (props) => {
const { t } = useTranslation();
const serviceContext = React.useContext(ServiceContext);
const addSubscription = useSubscriptions();
const [errorMessage, setErrorMessage] = React.useState('');
const [running, setRunning] = React.useState(false);

Check warning on line 64 in src/app/Dashboard/Diagnostics/DiagnosticsCard.tsx

View workflow job for this annotation

GitHub Actions / eslint-check (lts/*)

'setRunning' is assigned a value but never used. Allowed unused vars must match /^_/u
Josh-Matsuoka marked this conversation as resolved.
Show resolved Hide resolved
const isError = React.useMemo(() => errorMessage != '', [errorMessage]);

const handleError = React.useCallback(
(error) => {
setErrorMessage(error.message);
},
[setErrorMessage],
);

const handleGC = React.useCallback(() => {
addSubscription(
serviceContext.api.runGC().subscribe({
error: (err) => handleError(err),
}),
);
}, [addSubscription, serviceContext.api, handleError]);

const GCButton = React.useMemo(() => {
return (
<Button
key={0}
aria-label={t('DaignosticsCard.DIAGNOSTICS_GC_BUTTON', { chartKind: props.chartKind })}
Josh-Matsuoka marked this conversation as resolved.
Show resolved Hide resolved
onClick={handleGC}
Josh-Matsuoka marked this conversation as resolved.
Show resolved Hide resolved
variant="plain"
icon={<SyncAltIcon />}
isDisabled={false}
/>
);
}, [t, props.chartKind, handleGC]);

const actions = React.useMemo(() => {
const a = props.actions || [];
return [GCButton, ...a];
}, [props.actions, GCButton]);

const gcButtonLoadingProps = React.useMemo(
() =>
({
spinnerAriaValueText: 'Invoke GC',
spinnerAriaLabel: 'saving-credentials',
Josh-Matsuoka marked this conversation as resolved.
Show resolved Hide resolved
isLoading: running,
}) as LoadingProps,
[running],
);

const header = React.useMemo(() => {
return (
<CardHeader
actions={{
actions: <>{actions}</>,
hasNoOffset: false,
className: undefined,
}}
>
<CardTitle>
{t('DiagnosticsCard.DIAGNOSTICS_CARD_TITLE', {
chartKind: props.chartKind,
duration: props.duration,
period: props.period,
})}
</CardTitle>
</CardHeader>
);
}, [props.chartKind, props.duration, props.period, t, actions]);

return isError ? (
<ErrorView title={'Error executing diagnostic command'} message={`${errorMessage}`} />
) : (
<DashboardCard
id={props.chartKind + '-chart-card'}
dashboardId={props.dashboardId}
cardSizes={DiagnosticsCardSizes}
isCompact
cardHeader={header}
title={props.chartKind}
isDraggable={props.isDraggable}
isResizable={props.isResizable}
isFullHeight={props.isFullHeight}
>
<CardBody>
<Bullseye>
<EmptyState variant={EmptyStateVariant.lg}>
<EmptyStateHeader
titleText={<>{t('DiagnosticsCard.DIAGNOSTICS_CARD_TITLE')}</>}
icon={<EmptyStateIcon icon={DataSourceIcon} />}
Josh-Matsuoka marked this conversation as resolved.
Show resolved Hide resolved
headingLevel="h2"
/>
<EmptyStateBody>
<Trans t={t} components={{ label: <Label color="blue" isCompact /> }}>
DiagnosticsCard.DIAGNOSTICS_CARD_DESCRIPTION
</Trans>
</EmptyStateBody>
<EmptyStateFooter>
<Button variant="primary" onClick={handleGC} {...gcButtonLoadingProps}>
{t('DiagnosticsCard.DIAGNOSTICS_GC_BUTTON')}
</Button>
</EmptyStateFooter>
</EmptyState>
</Bullseye>
</CardBody>
</DashboardCard>
);
};

DiagnosticsCard.cardComponentName = 'DiagnosticsCard';

export const DiagnosticsCardSizes: DashboardCardSizes = {
span: {
minimum: 3,
default: 4,
maximum: 12,
},
height: {
// TODO: implement height resizing
minimum: Number.NaN,
default: Number.NaN,
maximum: Number.NaN,
},
};

export const DiagnosticsCardDescriptor: DashboardCardDescriptor = {
featureLevel: FeatureLevel.BETA,
title: 'DiagnosticsCard.DIAGNOSTICS_CARD_TITLE',
cardSizes: DiagnosticsCardSizes,
description: 'DiagnosticsCard.DIAGNOSTICS_CARD_DESCRIPTION',
descriptionFull: 'DiagnosticsCard.DIAGNOSTICS_CARD_DESCRIPTION_FULL',
component: DiagnosticsCard,
propControls: [],
icon: <TachometerAltIcon />,
Josh-Matsuoka marked this conversation as resolved.
Show resolved Hide resolved
labels: [
{
content: 'Beta',
color: 'cyan',
},
{
content: 'Diagnostics',
color: 'blue',
},
],
};
2 changes: 2 additions & 0 deletions src/app/Dashboard/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { AutomatedAnalysisCardDescriptor } from './AutomatedAnalysis/AutomatedAnalysisCard';
import { DiagnosticsCardDescriptor } from './Diagnostics/DiagnosticsCard';

Check warning on line 30 in src/app/Dashboard/utils.tsx

View workflow job for this annotation

GitHub Actions / eslint-check (lts/*)

`./Diagnostics/DiagnosticsCard` import should occur after import of `./Charts/mbean/MBeanMetricsChartCard`
import { JFRMetricsChartCardDescriptor } from './Charts/jfr/JFRMetricsChartCard';
import { MBeanMetricsChartCardDescriptor } from './Charts/mbean/MBeanMetricsChartCard';
import { JvmDetailsCardDescriptor } from './JvmDetails/JvmDetailsCard';
Expand Down Expand Up @@ -165,6 +166,7 @@
AutomatedAnalysisCardDescriptor,
JFRMetricsChartCardDescriptor,
MBeanMetricsChartCardDescriptor,
DiagnosticsCardDescriptor,
];
return cards.filter((card) => card.featureLevel >= featureLevel);
};
Expand Down
14 changes: 14 additions & 0 deletions src/app/Shared/Services/Api.service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,20 @@ export class ApiService {
);
}

runGC(): Observable<boolean> {
return this.target.target().pipe(
concatMap((target) =>
this.sendRequest('beta', `diagnostics/targets/${target?.id}/gc`, {
method: 'POST',
}).pipe(
map((resp) => resp.ok),
catchError(() => of(false)),
first(),
),
),
);
}

insertProbes(templateName: string): Observable<boolean> {
return this.target.target().pipe(
filter((t) => !!t),
Expand Down
Loading