Capturing more event clicks for scaffolder
Signed-off-by: bnechyporenko <bnechyporenko@bol.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Capturing more event clicks for scaffolder
|
||||
@@ -141,19 +141,25 @@
|
||||
},
|
||||
"presentation": {
|
||||
"type": "object",
|
||||
"description": "A way to redefine the labels for actionable buttons.",
|
||||
"description": "A way to redefine the presentation of the scaffolder.",
|
||||
"properties": {
|
||||
"backButtonText": {
|
||||
"type": "string",
|
||||
"description": "A button which return the user to one step back."
|
||||
},
|
||||
"createButtonText": {
|
||||
"type": "string",
|
||||
"description": "A button which start the execution of the template."
|
||||
},
|
||||
"reviewButtonText": {
|
||||
"type": "string",
|
||||
"description": "A button which open the review step to verify the input prior to start the execution."
|
||||
"buttonLabels": {
|
||||
"type": "object",
|
||||
"description": "A way to redefine the labels for actionable buttons.",
|
||||
"properties": {
|
||||
"backButtonText": {
|
||||
"type": "string",
|
||||
"description": "A button which return the user to one step back."
|
||||
},
|
||||
"createButtonText": {
|
||||
"type": "string",
|
||||
"description": "A button which start the execution of the template."
|
||||
},
|
||||
"reviewButtonText": {
|
||||
"type": "string",
|
||||
"description": "A button which open the review step to verify the input prior to start the execution."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -35,9 +35,8 @@ import {
|
||||
type FormValidation,
|
||||
} from './createAsyncValidators';
|
||||
import { ReviewState, type ReviewStateProps } from '../ReviewState';
|
||||
import { useTemplateSchema } from '../../hooks/useTemplateSchema';
|
||||
import { useTemplateSchema, useFormDataFromQuery } from '../../hooks';
|
||||
import validator from '@rjsf/validator-ajv8';
|
||||
import { useFormDataFromQuery } from '../../hooks';
|
||||
import { useTransformSchemaToProps } from '../../hooks/useTransformSchemaToProps';
|
||||
import { hasErrors } from './utils';
|
||||
import * as FieldOverrides from './FieldOverrides';
|
||||
@@ -115,6 +114,13 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
const [errors, setErrors] = useState<undefined | FormValidation>();
|
||||
const styles = useStyles();
|
||||
|
||||
const backLabel =
|
||||
presentation?.buttonLabels?.backButtonText ?? backButtonText;
|
||||
const createLabel =
|
||||
presentation?.buttonLabels?.createButtonText ?? createButtonText;
|
||||
const reviewLabel =
|
||||
presentation?.buttonLabels?.reviewButtonText ?? reviewButtonText;
|
||||
|
||||
const extensions = useMemo(() => {
|
||||
return Object.fromEntries(
|
||||
props.extensions.map(({ name, component }) => [name, component]),
|
||||
@@ -150,7 +156,8 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
|
||||
const handleCreate = useCallback(() => {
|
||||
props.onCreate(formState);
|
||||
}, [props, formState]);
|
||||
analytics.captureEvent('click', `${createLabel}`);
|
||||
}, [props, formState, analytics, createLabel]);
|
||||
|
||||
const currentStep = useTransformSchemaToProps(steps[activeStep], { layouts });
|
||||
|
||||
@@ -175,19 +182,13 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
setActiveStep(prevActiveStep => {
|
||||
const stepNum = prevActiveStep + 1;
|
||||
analytics.captureEvent('click', `Next Step (${stepNum})`);
|
||||
analytics.captureEvent('click', `Next Step (${stepNum})`);
|
||||
return stepNum;
|
||||
});
|
||||
}
|
||||
setFormState(current => ({ ...current, ...formData }));
|
||||
};
|
||||
|
||||
const backLabel =
|
||||
presentation?.buttonLabels?.backButtonText ?? backButtonText;
|
||||
const createLabel =
|
||||
presentation?.buttonLabels?.createButtonText ?? createButtonText;
|
||||
const reviewLabel =
|
||||
presentation?.buttonLabels?.reviewButtonText ?? reviewButtonText;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isValidating && <LinearProgress variant="indeterminate" />}
|
||||
@@ -214,7 +215,7 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
);
|
||||
})}
|
||||
<MuiStep>
|
||||
<MuiStepLabel>Review</MuiStepLabel>
|
||||
<MuiStepLabel>${reviewLabel}</MuiStepLabel>
|
||||
</MuiStep>
|
||||
</MuiStepper>
|
||||
<div className={styles.formWrapper}>
|
||||
@@ -274,7 +275,7 @@ export const Stepper = (stepperProps: StepperProps) => {
|
||||
className={styles.backButton}
|
||||
disabled={activeStep < 1}
|
||||
>
|
||||
Back
|
||||
{backLabel}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
|
||||
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { MarkdownContent, UserIcon } from '@backstage/core-components';
|
||||
import { IconComponent, useApp } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
IconComponent,
|
||||
useAnalytics,
|
||||
useApp,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityRefLinks,
|
||||
getEntityRelations,
|
||||
@@ -32,7 +36,7 @@ import Button from '@material-ui/core/Button';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import LanguageIcon from '@material-ui/icons/Language';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { CardHeader } from './CardHeader';
|
||||
import { CardLink } from './CardLink';
|
||||
|
||||
@@ -92,8 +96,9 @@ export interface TemplateCardProps {
|
||||
* @alpha
|
||||
*/
|
||||
export const TemplateCard = (props: TemplateCardProps) => {
|
||||
const { template } = props;
|
||||
const { onSelected, template } = props;
|
||||
const styles = useStyles();
|
||||
const analytics = useAnalytics();
|
||||
const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
|
||||
const app = useApp();
|
||||
const iconResolver = (key?: string): IconComponent =>
|
||||
@@ -103,6 +108,11 @@ export const TemplateCard = (props: TemplateCardProps) => {
|
||||
!!props.additionalLinks?.length || !!template.metadata.links?.length;
|
||||
const displayDefaultDivider = !hasTags && !hasLinks;
|
||||
|
||||
const handleChoose = useCallback(() => {
|
||||
analytics.captureEvent('click', `Template has been opened`);
|
||||
onSelected?.(template);
|
||||
}, [analytics, onSelected, template]);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader template={template} />
|
||||
@@ -190,7 +200,7 @@ export const TemplateCard = (props: TemplateCardProps) => {
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => props.onSelected?.(template)}
|
||||
onClick={handleChoose}
|
||||
>
|
||||
Choose
|
||||
</Button>
|
||||
|
||||
@@ -28,7 +28,7 @@ import Toc from '@material-ui/icons/Toc';
|
||||
import ControlPointIcon from '@material-ui/icons/ControlPoint';
|
||||
import MoreVert from '@material-ui/icons/MoreVert';
|
||||
import React, { useState } from 'react';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { useAnalytics, useApi } from '@backstage/core-plugin-api';
|
||||
import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
type ContextMenuProps = {
|
||||
@@ -61,10 +61,12 @@ export const ContextMenu = (props: ContextMenuProps) => {
|
||||
const pageTheme = getPageTheme({ themeId: 'website' });
|
||||
const classes = useStyles({ fontColor: pageTheme.fontColor });
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
const analytics = useAnalytics();
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>();
|
||||
|
||||
const [{ status: cancelStatus }, { execute: cancel }] = useAsync(async () => {
|
||||
if (taskId) {
|
||||
analytics.captureEvent('cancelled', 'Template has been cancelled');
|
||||
await scaffolderApi.cancelTask(taskId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
useTaskEventStream,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
import { selectedTemplateRouteRef } from '../../routes';
|
||||
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import { useAnalytics, useApi, useRouteRef } from '@backstage/core-plugin-api';
|
||||
import qs from 'qs';
|
||||
import { ContextMenu } from './ContextMenu';
|
||||
import {
|
||||
@@ -66,6 +66,7 @@ export const OngoingTask = (props: {
|
||||
const { taskId } = useParams();
|
||||
const templateRouteRef = useRouteRef(selectedTemplateRouteRef);
|
||||
const navigate = useNavigate();
|
||||
const analytics = useAnalytics();
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
const taskStream = useTaskEventStream(taskId!);
|
||||
const classes = useStyles();
|
||||
@@ -113,6 +114,8 @@ export const OngoingTask = (props: {
|
||||
return;
|
||||
}
|
||||
|
||||
analytics.captureEvent('click', `Task has been started over`);
|
||||
|
||||
navigate({
|
||||
pathname: templateRouteRef({
|
||||
namespace,
|
||||
@@ -121,6 +124,7 @@ export const OngoingTask = (props: {
|
||||
search: `?${qs.stringify({ formData: JSON.stringify(formData) })}`,
|
||||
});
|
||||
}, [
|
||||
analytics,
|
||||
navigate,
|
||||
taskStream.task?.spec.parameters,
|
||||
taskStream.task?.spec.templateInfo?.entity?.metadata,
|
||||
@@ -130,6 +134,7 @@ export const OngoingTask = (props: {
|
||||
const [{ status: cancelStatus }, { execute: triggerCancel }] = useAsync(
|
||||
async () => {
|
||||
if (taskId) {
|
||||
analytics.captureEvent('cancelled', 'Template has been cancelled');
|
||||
await scaffolderApi.cancelTask(taskId);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -129,19 +129,30 @@ describe('TemplateWizardPage', () => {
|
||||
fireEvent.click(await findByRole('button', { name: 'Create' }));
|
||||
});
|
||||
|
||||
// The "Next Step" button should have fired an event
|
||||
// The "Next Step" button should have fired few events
|
||||
expect(analyticsMock.getEvents()[0]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'Next Step (1)',
|
||||
context: { entityRef: 'template:default/test' },
|
||||
});
|
||||
|
||||
// And the "Create" button should have fired an event
|
||||
expect(analyticsMock.getEvents()[1]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'Next Step (1)',
|
||||
context: { entityRef: 'template:default/test' },
|
||||
});
|
||||
|
||||
// And the "Create" button should have fired few event
|
||||
expect(analyticsMock.getEvents()[2]).toMatchObject({
|
||||
action: 'create',
|
||||
subject: 'expected-name',
|
||||
context: { entityRef: 'template:default/test' },
|
||||
value: 120,
|
||||
});
|
||||
|
||||
expect(analyticsMock.getEvents()[3]).toMatchObject({
|
||||
action: 'click',
|
||||
subject: 'Create',
|
||||
context: { entityRef: 'template:default/test' },
|
||||
});
|
||||
});
|
||||
describe('scaffolder page context menu', () => {
|
||||
|
||||
Reference in New Issue
Block a user