scaffolder: refine analytics events data

Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
Vincenzo Scamporlino
2025-06-13 10:00:42 +02:00
parent df766a420e
commit 2da89d1289
4 changed files with 63 additions and 40 deletions
@@ -22,7 +22,7 @@ import {
} from '@backstage/core-components';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { TemplateCardProps, TemplateCard } from '../TemplateCard';
import { IconComponent } from '@backstage/core-plugin-api';
import { AnalyticsContext, IconComponent } from '@backstage/core-plugin-api';
/**
* The props for the {@link TemplateGroup} component.
@@ -69,12 +69,18 @@ export const TemplateGroup = (props: TemplateGroupProps) => {
{titleComponent}
<ItemCardGrid>
{templates.map(({ template, additionalLinks }) => (
<Card
<AnalyticsContext
attributes={{
entityRef: stringifyEntityRef(template),
}}
key={stringifyEntityRef(template)}
additionalLinks={additionalLinks}
template={template}
onSelected={onSelected}
/>
>
<Card
additionalLinks={additionalLinks}
template={template}
onSelected={onSelected}
/>
</AnalyticsContext>
))}
</ItemCardGrid>
</Content>
@@ -97,13 +97,11 @@ export const Workflow = (workflowProps: WorkflowProps): JSX.Element | null => {
async (formState: Record<string, JsonValue>) => {
await onCreate(formState);
const name =
typeof formState.name === 'string' ? formState.name : undefined;
analytics.captureEvent('create', name ?? templateName ?? 'unknown', {
analytics.captureEvent('create', 'Task has been created', {
value: minutesSaved,
});
},
[onCreate, analytics, templateName, minutesSaved],
[onCreate, analytics, minutesSaved],
);
useEffect(() => {
@@ -29,11 +29,8 @@ import Toc from '@material-ui/icons/Toc';
import ControlPointIcon from '@material-ui/icons/ControlPoint';
import MoreVert from '@material-ui/icons/MoreVert';
import { SyntheticEvent, useState } from 'react';
import { useAnalytics, useApi } from '@backstage/core-plugin-api';
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
import { usePermission } from '@backstage/plugin-permission-react';
import {
taskCancelPermission,
taskReadPermission,
taskCreatePermission,
} from '@backstage/plugin-scaffolder-common/alpha';
@@ -50,7 +47,8 @@ type ContextMenuProps = {
onStartOver?: () => void;
onToggleLogs?: (state: boolean) => void;
onToggleButtonBar?: (state: boolean) => void;
taskId?: string;
isCancelButtonDisabled: boolean;
onCancel: () => void;
};
const useStyles = makeStyles<Theme, { fontColor: string }>(() => ({
@@ -70,27 +68,13 @@ export const ContextMenu = (props: ContextMenuProps) => {
onStartOver,
onToggleLogs,
onToggleButtonBar,
taskId,
} = props;
const { getPageTheme } = useTheme();
const pageTheme = getPageTheme({ themeId: 'website' });
const classes = useStyles({ fontColor: pageTheme.fontColor });
const scaffolderApi = useApi(scaffolderApiRef);
const analytics = useAnalytics();
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>();
const { t } = useTranslationRef(scaffolderTranslationRef);
const [{ status: cancelStatus }, { execute: cancel }] = useAsync(async () => {
if (taskId) {
analytics.captureEvent('cancelled', 'Template has been cancelled');
await scaffolderApi.cancelTask(taskId);
}
});
const { allowed: canCancelTask } = usePermission({
permission: taskCancelPermission,
});
const { allowed: canReadTask } = usePermission({
permission: taskReadPermission,
});
@@ -171,12 +155,8 @@ export const ContextMenu = (props: ContextMenuProps) => {
</MenuItem>
)}
<MenuItem
onClick={cancel}
disabled={
!cancelEnabled ||
cancelStatus !== 'not-executed' ||
!canCancelTask
}
onClick={props.onCancel}
disabled={props.isCancelButtonDisabled}
data-testid="cancel-task"
>
<ListItemIcon>
@@ -32,7 +32,12 @@ import {
useTaskEventStream,
} from '@backstage/plugin-scaffolder-react';
import { selectedTemplateRouteRef } from '../../routes';
import { useAnalytics, useApi, useRouteRef } from '@backstage/core-plugin-api';
import {
AnalyticsContext,
useAnalytics,
useApi,
useRouteRef,
} from '@backstage/core-plugin-api';
import qs from 'qs';
import { ContextMenu } from './ContextMenu';
import {
@@ -51,6 +56,7 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
import { scaffolderTranslationRef } from '../../translation';
import { entityPresentationApiRef } from '@backstage/plugin-catalog-react';
import { default as reactUseAsync } from 'react-use/esm/useAsync';
import { stringifyEntityRef } from '@backstage/catalog-model';
const useStyles = makeStyles(theme => ({
contentWrapper: {
@@ -82,6 +88,36 @@ export const OngoingTask = (props: {
}>;
}) => {
// todo(blam): check that task Id actually exists, and that it's valid. otherwise redirect to something more useful.
const { taskId } = useParams();
const taskStream = useTaskEventStream(taskId!);
const { namespace, name } =
taskStream.task?.spec.templateInfo?.entity?.metadata ?? {};
return (
<AnalyticsContext
attributes={{
entityRef:
name &&
stringifyEntityRef({
kind: 'template',
namespace,
name,
}),
taskId,
}}
>
<Page themeId="website">
<OngoingTaskContent {...props} />
</Page>
</AnalyticsContext>
);
};
function OngoingTaskContent(props: {
TemplateOutputsComponent?: ComponentType<{
output?: ScaffolderTaskOutput;
}>;
}) {
const { taskId } = useParams();
const templateRouteRef = useRouteRef(selectedTemplateRouteRef);
const navigate = useNavigate();
@@ -183,7 +219,7 @@ export const OngoingTask = (props: {
templateRouteRef,
]);
const [{ status: _ }, { execute: triggerRetry }] = useAsync(async () => {
const [, { execute: triggerRetry }] = useAsync(async () => {
if (taskId) {
analytics.captureEvent('retried', 'Template has been retried');
await scaffolderApi.retry?.(taskId);
@@ -202,9 +238,11 @@ export const OngoingTask = (props: {
const Outputs = props.TemplateOutputsComponent ?? DefaultTemplateOutputs;
const cancelEnabled = !(taskStream.cancelled || taskStream.completed);
const isCancelButtonDisabled =
!cancelEnabled || cancelStatus !== 'not-executed' || !canCancelTask;
return (
<Page themeId="website">
<>
<Header
pageTitleOverride={
presentation
@@ -231,7 +269,8 @@ export const OngoingTask = (props: {
onRetry={triggerRetry}
onToggleLogs={setLogVisibleState}
onToggleButtonBar={setButtonBarVisibleState}
taskId={taskId}
onCancel={triggerCancel}
isCancelButtonDisabled={isCancelButtonDisabled}
/>
</Header>
<Content className={classes.contentWrapper}>
@@ -316,6 +355,6 @@ export const OngoingTask = (props: {
</Paper>
) : null}
</Content>
</Page>
</>
);
};
}