The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/public/app/features/gops/configuration-tracker/components/Essentials.tsx

196 lines
5.4 KiB

Gops: Add configuration tracker on the existing IRM page (#85838) * WIP * add configure irm componet and pass it to Nav * add check if theres datasource for Alerts * update incident steps links * uncomment done property in the interface * Fix not having data sources in store and removing warning using wrong Icon name type * call incidents api in done step * Show checked steps with an icon instead of a badge * Implemnt check contact point ready in essentials * Fix codeowners and prettify file * Check if there is one oncall integration in at least one contact point * Refactor * Check for oncall integration being created * Add the test oncall integration action * Do not hide any card in irm page, and refactor * Refactor: move hooks to a separate file * Implement oncall chatops checks * update incidents hook * Show new irm page only on cloud and for admins * fix prettier in irmhooks * remove unused import * fix prettier in irm hooks * fix axios method * Add progress bar on essentials * Update texts and some styles * Refactor * fix api call * fix check is plugin is installed * fix async call * fix lint * Do not show check icon when done field is undefined in a step * refactor * Add test for Essentials * check if incident plugin installed * call incidents api to get steps * add the new api to get config * fix prettier * memoize the api call * fix lint * add proper api call * check if response is valid * fix typo * use state to save the values * fix lint * fix response schema * fix prettier * update incident steps copy * udapte texts in respond tooltips * Fix confiure IRM route check * Fix logic for the data source check: check if there is a data source that is alerting compatible * Use existing header prop in NavLandingPage instead of creating a new prop * fix wrong updated file * Update logic for default contact point check and update some links * Update texts and show only one item for oncall integration with alerting checks * Update texts following suggestions in the doc * Fix getting default contact point and update oncall link for slack tab * Update texts, buttons and checks following last meeting action items * remove unnecessary component drawer * Track interactions: user open or close essentials drawer * Refactor * remove unnecessary createMonitoringLogger for tracking irm events * remove unnecessary style * refactor * refactor * Add fallback links and labels for action buttons when step is done * Update irm card styles * remove extra space after progress bar numbers * remove progress bar border * Address pr review comments * remove unnecessary AlertmanagerProvider * fix logic behind default contact point check * update test * Address pr review comments part1 * add aria and properties role for Progressbar * Reorganize hooks into separate folders/files for each app * move done field to the step level * Handle empty dropdown options * Handle loading state * Update tooltip for connecting alerting to oncall * Use RTKQ for incidents * handle loading for oncall hooks * refactor getting configuration for each app * fix incident rtkq query to be a query instead of mutation * refactor: rename variable * Address some nits in the review --------- Co-authored-by: reemtariqq <reem.tariq@grafana.com>
2 years ago
import { css } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Drawer, Dropdown, Icon, LinkButton, Menu, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui';
import { createUrl } from 'app/features/alerting/unified/utils/url';
import { SectionDto, SectionDtoStep, SectionsDto, StepButtonDto } from '../irmHooks';
import { ProgressBar, StepsStatus } from './ProgressBar';
export interface EssentialsProps {
onClose: () => void;
essentialsConfig: SectionsDto;
stepsDone: number;
totalStepsToDo: number;
}
export function Essentials({ onClose, essentialsConfig, stepsDone, totalStepsToDo }: EssentialsProps) {
return (
<Drawer title="Essentials" subtitle="Complete the following configuration tasks" onClose={onClose}>
<EssentialContent essentialContent={essentialsConfig} stepsDone={stepsDone} totalStepsToDo={totalStepsToDo} />
</Drawer>
);
}
export function EssentialContent({
essentialContent,
stepsDone,
totalStepsToDo,
}: {
essentialContent: SectionsDto;
stepsDone: number;
totalStepsToDo: number;
}) {
return (
<Stack direction={'column'} gap={1}>
<ProgressStatus stepsDone={stepsDone} totalStepsToDo={totalStepsToDo} />
{essentialContent.sections.map((section: SectionDto) => (
<Section key={section.title} section={section} />
))}
</Stack>
);
}
interface SectionProps {
section: SectionDto;
}
function Section({ section }: SectionProps) {
const styles = useStyles2(getStyles);
return (
<div className={styles.wrapper}>
<Text element="h4">{section.title}</Text>
<Text color="secondary">{section.description}</Text>
<Stack direction={'column'} gap={2}>
{section.steps.map((step, index) => (
<Step key={index} step={step} />
))}
</Stack>
</div>
);
}
function DoneIcon({ done }: { done: boolean }) {
return done ? (
<Icon name="check-circle" color="green" data-testid="checked-step" />
) : (
<Icon name="circle" data-testid="unckecked-step" />
);
}
interface StepProps {
step: SectionDtoStep;
}
function Step({ step }: StepProps) {
return (
<Stack direction={'row'} justifyContent={'space-between'} data-testid="step">
<Stack direction={'row'} alignItems="center">
{step.done !== undefined && <DoneIcon done={step.done} />}
<Text variant="body">{step.title}</Text>
<Tooltip content={step.description} placement="right">
<Icon name="question-circle" />
</Tooltip>
</Stack>
<StepButton {...step.button} done={step.done} data-testid="step-button" />
</Stack>
);
}
interface LinkButtonProps {
urlLink?: { url: string; queryParams?: Record<string, string> };
label: string;
urlLinkOnDone?: { url: string; queryParams?: Record<string, string> };
labelOnDone?: string;
done?: boolean;
}
function OpenLinkButton(props: LinkButtonProps) {
const { urlLink, label, urlLinkOnDone, labelOnDone, done } = props;
const urlToGoWhenNotDone = urlLink?.url
? createUrl(urlLink.url, {
returnTo: location.pathname + location.search,
...urlLink.queryParams,
})
: '';
const urlToGoWhenDone = urlLinkOnDone?.url
? createUrl(urlLinkOnDone.url, {
returnTo: location.pathname + location.search,
...urlLinkOnDone.queryParams,
})
: '';
const urlToGo = done ? urlToGoWhenDone : urlToGoWhenNotDone;
return (
<LinkButton href={urlToGo} variant="secondary">
{done ? labelOnDone ?? label : label}
</LinkButton>
);
}
interface StepButtonProps extends StepButtonDto {
done?: boolean;
}
function StepButton({
type,
urlLink,
urlLinkOnDone,
label,
labelOnDone,
options,
onClickOption,
done,
stepNotAvailableText,
}: StepButtonProps) {
switch (type) {
case 'openLink':
return (
<OpenLinkButton
urlLink={urlLink}
label={label}
urlLinkOnDone={urlLinkOnDone}
labelOnDone={labelOnDone}
done={done}
/>
);
case 'dropDown':
if (Boolean(options?.length)) {
return (
<Dropdown
overlay={
<Menu>
{options?.map((option) => (
<Menu.Item
label={option.label}
key={option.value}
onClick={() => {
onClickOption?.(option.value);
}}
/>
))}
</Menu>
}
>
<Button variant="secondary" size="md">
{label}
<Icon name="angle-down" />
</Button>
</Dropdown>
);
} else {
return <Text>{stepNotAvailableText ?? 'No options available'} </Text>;
}
}
}
function ProgressStatus({ stepsDone, totalStepsToDo }: { stepsDone: number; totalStepsToDo: number }) {
return (
<Stack direction={'row'} gap={1} alignItems="center">
Your progress
<ProgressBar stepsDone={stepsDone} totalStepsToDo={totalStepsToDo} />
<StepsStatus stepsDone={stepsDone} totalStepsToDo={totalStepsToDo} />
</Stack>
);
}
const getStyles = (theme: GrafanaTheme2) => {
return {
wrapper: css({
margin: theme.spacing(2, 0),
padding: theme.spacing(2),
border: `1px solid ${theme.colors.border.medium}`,
borderRadius: theme.shape.radius.default,
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
}),
};
};