diff --git a/ui/.betterer.results b/ui/.betterer.results index 7eb68d235e..9306ac4f52 100644 --- a/ui/.betterer.results +++ b/ui/.betterer.results @@ -7,7 +7,7 @@ exports[`stricter compilation`] = { [162, 4, 36, "Object is possibly \'null\'.", "1039669632"] ], "src/app/App.tsx:3872899616": [ - [21, 7, 25, "Could not find a declaration file for module \'@maas-ui/maas-ui-shared\'. \'/Users/kit/src/canonical/local/maas-ui/shared/dist/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/maas-ui__maas-ui-shared\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@maas-ui/maas-ui-shared\';\`", "1778274862"], + [21, 7, 25, "Could not find a declaration file for module \'@maas-ui/maas-ui-shared\'. \'/home/caleb/Projects/maas-ui/shared/dist/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/maas-ui__maas-ui-shared\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@maas-ui/maas-ui-shared\';\`", "1778274862"], [188, 17, 17, "Object is possibly \'null\'.", "2133029343"], [193, 7, 7, "Variable \'content\' is used before being assigned.", "3716929964"] ], @@ -25,14 +25,14 @@ exports[`stricter compilation`] = { [132, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "1015219919"], [132, 6, 41, "Expected 1 arguments, but got 0.", "1767895374"] ], - "src/app/base/components/ActionForm/ActionForm.tsx:1503436556": [ + "src/app/base/components/ActionForm/ActionForm.tsx:2395460442": [ [16, 21, 15, "Object is possibly \'undefined\'.", "1847077069"], [20, 20, 13, "Object is possibly \'undefined\'.", "3155939599"], [23, 6, 13, "Object is possibly \'undefined\'.", "3155939599"], [23, 22, 15, "Object is possibly \'undefined\'.", "1847077069"], [25, 13, 13, "Object is possibly \'undefined\'.", "3155939599"], - [130, 39, 6, "Argument of type \'{ [x: string]: any; } | undefined\' is not assignable to parameter of type \'string | object | string[]\'.\\n Type \'undefined\' is not assignable to type \'string | object | string[]\'.", "1168132398"], - [133, 4, 15, "Argument of type \'number | undefined\' is not assignable to parameter of type \'number\'.\\n Type \'undefined\' is not assignable to type \'number\'.", "1847077069"] + [132, 39, 6, "Argument of type \'{ [x: string]: any; } | undefined\' is not assignable to parameter of type \'string | object | string[]\'.\\n Type \'undefined\' is not assignable to type \'string | object | string[]\'.", "1168132398"], + [135, 4, 15, "Argument of type \'number | undefined\' is not assignable to parameter of type \'number\'.\\n Type \'undefined\' is not assignable to type \'number\'.", "1847077069"] ], "src/app/base/components/DoughnutChart/DoughnutChart.tsx:1593454831": [ [86, 8, 11, "Type \'(() => void) | null\' is not assignable to type \'((event: MouseEvent) => void) | undefined\'.\\n Type \'null\' is not assignable to type \'((event: MouseEvent) => void) | undefined\'.", "2109816907"], @@ -44,7 +44,7 @@ exports[`stricter compilation`] = { [75, 4, 5, "Argument of type \'boolean | undefined\' is not assignable to parameter of type \'boolean\'.\\n Type \'undefined\' is not assignable to type \'boolean\'.", "195688512"] ], "src/app/base/components/LegacyLink/LegacyLink.tsx:2706551295": [ - [4, 52, 25, "Could not find a declaration file for module \'@maas-ui/maas-ui-shared\'. \'/Users/kit/src/canonical/local/maas-ui/shared/dist/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/maas-ui__maas-ui-shared\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@maas-ui/maas-ui-shared\';\`", "1778274862"] + [4, 52, 25, "Could not find a declaration file for module \'@maas-ui/maas-ui-shared\'. \'/home/caleb/Projects/maas-ui/shared/dist/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/maas-ui__maas-ui-shared\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@maas-ui/maas-ui-shared\';\`", "1778274862"] ], "src/app/base/components/NotificationGroup/Notification/Notification.tsx:122297593": [ [26, 26, 12, "Argument of type \'Notification | null\' is not assignable to parameter of type \'Notification\'.\\n Type \'null\' is not assignable to type \'Notification\'.\\n Type \'null\' is not assignable to type \'Model\'.", "148512008"], @@ -86,7 +86,7 @@ exports[`stricter compilation`] = { [214, 7, 11, "Property \'placeholder\' is missing in type \'{ disabledTags: { id: number; name: string; }[]; initialSelected: { id: number; name: string; }[]; tags: { id: number; name: string; }[]; }\' but required in type \'Props\'.", "3766634306"] ], "src/app/base/components/TagSelector/TagSelector.tsx:2755544058": [ - [1, 18, 51, "Could not find a declaration file for module \'@canonical/react-components/dist/components/Field\'. \'/Users/kit/src/canonical/local/maas-ui/node_modules/@canonical/react-components/dist/components/Field/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/canonical__react-components\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@canonical/react-components/dist/components/Field\';\`", "1535046059"], + [1, 18, 51, "Could not find a declaration file for module \'@canonical/react-components/dist/components/Field\'. \'/home/caleb/Projects/maas-ui/node_modules/@canonical/react-components/dist/components/Field/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/canonical__react-components\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@canonical/react-components/dist/components/Field\';\`", "1535046059"], [37, 2, 12, "Binding element \'allowNewTags\' implicitly has an \'any\' type.", "3979358209"], [38, 2, 6, "Binding element \'filter\' implicitly has an \'any\' type.", "1355726373"], [39, 2, 12, "Binding element \'selectedTags\' implicitly has an \'any\' type.", "2698915821"], @@ -106,7 +106,7 @@ exports[`stricter compilation`] = { [226, 21, 1, "Parameter \'e\' implicitly has an \'any\' type.", "177600"], [228, 23, 1, "Parameter \'e\' implicitly has an \'any\' type.", "177600"] ], - "src/app/kvm/components/KVMActionFormWrapper/ComposeForm/ComposeForm.tsx:527913969": [ + "src/app/kvm/components/KVMActionFormWrapper/ComposeForm/ComposeForm.tsx:1415357288": [ [138, 16, 4, "Object is possibly \'undefined\'.", "2087386672"], [138, 27, 4, "Object is possibly \'undefined\'.", "2087386672"], [138, 41, 4, "Object is possibly \'undefined\'.", "2087386672"], @@ -114,7 +114,7 @@ exports[`stricter compilation`] = { [215, 8, 20, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'{}\'.\\n No index signature with a parameter of type \'string\' was found on type \'{}\'.", "4165046927"], [263, 34, 27, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'{}\'.\\n No index signature with a parameter of type \'string\' was found on type \'{}\'.", "4259134870"], [272, 19, 5, "Variable \'error\' is used before being assigned.", "165548477"], - [314, 8, 8, "Type \'(values: ComposeFormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'ComposeFormValues\'.", "1301647696"] + [319, 8, 8, "Type \'(values: ComposeFormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'ComposeFormValues\'.", "1301647696"] ], "src/app/kvm/components/KVMActionFormWrapper/ComposeForm/InterfacesTable/InterfacesTable.test.tsx:3478481718": [ [94, 36, 5, "Argument of type \'MockStoreEnhanced\' is not assignable to parameter of type \'MockStoreEnhanced<{}, {}>\'.\\n Type \'MockStoreEnhanced\' is not assignable to type \'MockStore<{}, AnyAction>\'.\\n The types returned by \'getState()\' are incompatible between these types.\\n Type \'unknown\' is not assignable to type \'{}\'.", "195037722"], @@ -246,36 +246,36 @@ exports[`stricter compilation`] = { [130, 2, 656, "Type \'Element | null\' is not assignable to type \'Element\'.\\n Type \'null\' is not assignable to type \'Element\'.", "2065603965"], [144, 36, 11, "Property \'setSelected\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "1023496814"] ], - "src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.test.tsx:2858846500": [ + "src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.test.tsx:1854654149": [ [106, 6, 66, "Cannot invoke an object which is possibly \'undefined\'.", "4166470712"], [110, 10, 15, "Argument of type \'{ enableSSH: boolean; skipBMCConfig: boolean; skipNetworking: boolean; skipStorage: boolean; updateFirmware: boolean; configureHBA: boolean; testingScripts: Scripts[]; commissioningScripts: Scripts[]; scriptInputs: { ...; }; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'enableSSH\' does not exist in type \'FormEvent<{}>\'.", "2907345984"], [201, 6, 66, "Cannot invoke an object which is possibly \'undefined\'.", "4166470712"], [205, 10, 15, "Argument of type \'{ enableSSH: boolean; skipBMCConfig: boolean; skipNetworking: boolean; skipStorage: boolean; updateFirmware: boolean; configureHBA: boolean; testingScripts: Scripts[]; commissioningScripts: Scripts[]; scriptInputs: { ...; }; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'enableSSH\' does not exist in type \'FormEvent<{}>\'.", "2907345984"] ], - "src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.tsx:1892825711": [ - [121, 6, 8, "Type \'(values: CommissionFormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'CommissionFormValues\'.", "1301647696"], - [135, 27, 10, "Property \'commission\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "2592181864"], - [155, 42, 25, "Argument of type \'(Scripts | undefined)[]\' is not assignable to parameter of type \'Scripts[]\'.\\n Type \'Scripts | undefined\' is not assignable to type \'Scripts\'.\\n Type \'undefined\' is not assignable to type \'Scripts\'.\\n Type \'undefined\' is not assignable to type \'Model\'.", "4025185665"] - ], - "src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.test.tsx:1194028480": [ - [139, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], - [140, 8, 17, "Argument of type \'{ oSystem: string; release: string; kernel: string; installKVM: boolean; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'oSystem\' does not exist in type \'FormEvent<{}>\'.", "4147515896"], - [210, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], - [211, 8, 17, "Argument of type \'{ oSystem: string; release: string; kernel: string; installKVM: boolean; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'oSystem\' does not exist in type \'FormEvent<{}>\'.", "4147515896"], - [257, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], - [258, 8, 21, "Argument of type \'{ includeUserData: boolean; installKVM: boolean; kernel: string; oSystem: string; release: string; userData: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'includeUserData\' does not exist in type \'FormEvent<{}>\'.", "4050162228"], - [306, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], - [307, 8, 22, "Argument of type \'{ includeUserData: boolean; installKVM: boolean; kernel: string; oSystem: string; release: string; userData: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'includeUserData\' does not exist in type \'FormEvent<{}>\'.", "486161855"], - [355, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], - [356, 8, 21, "Argument of type \'{ includeUserData: boolean; installKVM: boolean; kernel: string; oSystem: string; release: string; userData: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'includeUserData\' does not exist in type \'FormEvent<{}>\'.", "4050162228"] - ], - "src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.tsx:2382076300": [ - [97, 6, 8, "Type \'(values: DeployFormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'DeployFormValues\'.", "1301647696"], - [115, 34, 6, "Property \'deploy\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "1121484462"] - ], - "src/app/machines/components/ActionFormWrapper/DeployForm/DeployFormFields/UserDataField/UserDataField.test.tsx:2053903631": [ - [116, 10, 19, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'{}\'.\\n No index signature with a parameter of type \'string\' was found on type \'{}\'.", "2609332470"], - [150, 12, 10, "Type \'{ processing: boolean; setProcessing: Mock; setSelectedAction: Mock; }\' is not assignable to type \'IntrinsicAttributes & Pick & Pick any>; }>, never> & Pick<...>\'.\\n Property \'processing\' does not exist on type \'IntrinsicAttributes & Pick & Pick any>; }>, never> & Pick<...>\'.", "3107157038"] + "src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.tsx:343946748": [ + [127, 6, 8, "Type \'(values: CommissionFormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'CommissionFormValues\'.", "1301647696"], + [141, 27, 10, "Property \'commission\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "2592181864"], + [161, 42, 25, "Argument of type \'(Scripts | undefined)[]\' is not assignable to parameter of type \'Scripts[]\'.\\n Type \'Scripts | undefined\' is not assignable to type \'Scripts\'.\\n Type \'undefined\' is not assignable to type \'Scripts\'.\\n Type \'undefined\' is not assignable to type \'Model\'.", "4025185665"] + ], + "src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.test.tsx:2511798015": [ + [149, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], + [150, 8, 17, "Argument of type \'{ oSystem: string; release: string; kernel: string; installKVM: boolean; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'oSystem\' does not exist in type \'FormEvent<{}>\'.", "4147515896"], + [220, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], + [221, 8, 17, "Argument of type \'{ oSystem: string; release: string; kernel: string; installKVM: boolean; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'oSystem\' does not exist in type \'FormEvent<{}>\'.", "4147515896"], + [267, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], + [268, 8, 21, "Argument of type \'{ includeUserData: boolean; installKVM: boolean; kernel: string; oSystem: string; release: string; userData: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'includeUserData\' does not exist in type \'FormEvent<{}>\'.", "4050162228"], + [316, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], + [317, 8, 22, "Argument of type \'{ includeUserData: boolean; installKVM: boolean; kernel: string; oSystem: string; release: string; userData: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'includeUserData\' does not exist in type \'FormEvent<{}>\'.", "486161855"], + [354, 40, 16, "Cannot assign to \'useSendAnalytics\' because it is a read-only property.", "3235495692"], + [369, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], + [370, 8, 21, "Argument of type \'{ includeUserData: boolean; installKVM: boolean; kernel: string; oSystem: string; release: string; userData: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'includeUserData\' does not exist in type \'FormEvent<{}>\'.", "4050162228"] + ], + "src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.tsx:2753772467": [ + [102, 6, 8, "Type \'(values: DeployFormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'DeployFormValues\'.", "1301647696"], + [120, 34, 6, "Property \'deploy\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "1121484462"] + ], + "src/app/machines/components/ActionFormWrapper/DeployForm/DeployFormFields/UserDataField/UserDataField.test.tsx:3711365735": [ + [119, 10, 19, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'{}\'.\\n No index signature with a parameter of type \'string\' was found on type \'{}\'.", "2609332470"] ], "src/app/machines/components/ActionFormWrapper/DeployForm/DeployFormFields/UserDataField/UserDataField.tsx:3781897231": [ [19, 27, 4, "Binding element \'file\' implicitly has an \'any\' type.", "2087597251"], @@ -286,29 +286,29 @@ exports[`stricter compilation`] = { [67, 4, 14, "Type \'([file]: [any]) => void\' is not assignable to type \'(files: T[], event: DropEvent) => void\'.\\n Types of parameters \'__0\' and \'files\' are incompatible.\\n Type \'T[]\' is not assignable to type \'[any]\'.\\n Target requires 1 element(s) but source may have fewer.", "3837758380"], [100, 34, 5, "Type \'null\' is not assignable to type \'CSSProperties | undefined\'.", "195056594"] ], - "src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.test.tsx:2328625110": [ - [63, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], - [64, 8, 29, "Argument of type \'{ comment: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'comment\' does not exist in type \'FormEvent<{}>\'.", "940323754"], - [118, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], - [119, 8, 11, "Argument of type \'{ comment: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'comment\' does not exist in type \'FormEvent<{}>\'.", "942845580"], - [163, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], - [164, 8, 29, "Argument of type \'{ comment: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'comment\' does not exist in type \'FormEvent<{}>\'.", "940323754"] - ], - "src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.tsx:4230109469": [ - [50, 6, 8, "Type \'(values: MarkBrokenFormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'MarkBrokenFormValues\'.", "1301647696"], - [53, 27, 10, "Property \'markBroken\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "3004692879"] - ], - "src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx:3774525721": [ - [95, 6, 66, "Cannot invoke an object which is possibly \'undefined\'.", "4166470712"], - [99, 10, 15, "Argument of type \'{ enableSSH: boolean; scripts: Scripts[]; scriptInputs: { \\"internet-connectivity\\": string; }; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'enableSSH\' does not exist in type \'FormEvent<{}>\'.", "2907345984"], - [216, 6, 66, "Cannot invoke an object which is possibly \'undefined\'.", "4166470712"], - [220, 10, 15, "Argument of type \'{ enableSSH: boolean; scripts: Scripts[]; scriptInputs: { \\"internet-connectivity\\": string; }; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'enableSSH\' does not exist in type \'FormEvent<{}>\'.", "2907345984"] - ], - "src/app/machines/components/ActionFormWrapper/TestForm/TestForm.tsx:739278699": [ - [76, 6, 25, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'{}\'.\\n No index signature with a parameter of type \'string\' was found on type \'{}\'.", "311081487"], - [99, 6, 8, "Type \'(values: FormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'FormValues\'.", "1301647696"], - [103, 27, 4, "Property \'test\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "2087956275"], - [116, 22, 11, "Type \'({ displayName: string; id: number; apply_configured_networking: boolean; default: boolean; description: string; destructive: boolean; for_hardware: string[]; hardware_type_name: \\"Node\\" | \\"CPU\\" | \\"Memory\\" | \\"Storage\\" | \\"Network\\"; ... 15 more ...; type: number; } | undefined)[]\' is not assignable to type \'ScriptsDisplay[]\'.\\n Type \'{ displayName: string; id: number; apply_configured_networking: boolean; default: boolean; description: string; destructive: boolean; for_hardware: string[]; hardware_type_name: \\"Node\\" | \\"CPU\\" | \\"Memory\\" | \\"Storage\\" | \\"Network\\"; ... 15 more ...; type: number; } | undefined\' is not assignable to type \'ScriptsDisplay\'.\\n Type \'undefined\' is not assignable to type \'ScriptsDisplay\'.\\n Type \'undefined\' is not assignable to type \'Model\'.", "611253867"] + "src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.test.tsx:982499928": [ + [64, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], + [65, 8, 29, "Argument of type \'{ comment: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'comment\' does not exist in type \'FormEvent<{}>\'.", "940323754"], + [119, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], + [120, 8, 11, "Argument of type \'{ comment: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'comment\' does not exist in type \'FormEvent<{}>\'.", "942845580"], + [164, 6, 39, "Cannot invoke an object which is possibly \'undefined\'.", "2019447570"], + [165, 8, 29, "Argument of type \'{ comment: string; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'comment\' does not exist in type \'FormEvent<{}>\'.", "940323754"] + ], + "src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.tsx:2501300585": [ + [56, 6, 8, "Type \'(values: MarkBrokenFormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'MarkBrokenFormValues\'.", "1301647696"], + [59, 27, 10, "Property \'markBroken\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "3004692879"] + ], + "src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx:3219855266": [ + [94, 6, 66, "Cannot invoke an object which is possibly \'undefined\'.", "4166470712"], + [98, 10, 15, "Argument of type \'{ enableSSH: boolean; scripts: Scripts[]; scriptInputs: { \\"internet-connectivity\\": string; }; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'enableSSH\' does not exist in type \'FormEvent<{}>\'.", "2907345984"], + [215, 6, 66, "Cannot invoke an object which is possibly \'undefined\'.", "4166470712"], + [219, 10, 15, "Argument of type \'{ enableSSH: boolean; scripts: Scripts[]; scriptInputs: { \\"internet-connectivity\\": string; }; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'enableSSH\' does not exist in type \'FormEvent<{}>\'.", "2907345984"] + ], + "src/app/machines/components/ActionFormWrapper/TestForm/TestForm.tsx:215573987": [ + [77, 6, 25, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'{}\'.\\n No index signature with a parameter of type \'string\' was found on type \'{}\'.", "311081487"], + [105, 6, 8, "Type \'(values: FormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'FormValues\'.", "1301647696"], + [109, 27, 4, "Property \'test\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "2087956275"], + [122, 22, 11, "Type \'({ displayName: string; id: number; apply_configured_networking: boolean; default: boolean; description: string; destructive: boolean; for_hardware: string[]; hardware_type_name: \\"Node\\" | \\"CPU\\" | \\"Memory\\" | \\"Storage\\" | \\"Network\\"; ... 15 more ...; type: number; } | undefined)[]\' is not assignable to type \'ScriptsDisplay[]\'.\\n Type \'{ displayName: string; id: number; apply_configured_networking: boolean; default: boolean; description: string; destructive: boolean; for_hardware: string[]; hardware_type_name: \\"Node\\" | \\"CPU\\" | \\"Memory\\" | \\"Storage\\" | \\"Network\\"; ... 15 more ...; type: number; } | undefined\' is not assignable to type \'ScriptsDisplay\'.\\n Type \'undefined\' is not assignable to type \'ScriptsDisplay\'.\\n Type \'undefined\' is not assignable to type \'Model\'.", "611253867"] ], "src/app/machines/components/TakeActionMenu/TakeActionMenu.test.tsx:2594365837": [ [77, 8, 4, "Type \'\\"lifecycle1\\"\' is not assignable to type \'\\"on\\" | \\"off\\" | \\"abort\\" | \\"acquire\\" | \\"check-power\\" | \\"commission\\" | \\"delete\\" | \\"deploy\\" | \\"exit-rescue-mode\\" | \\"lock\\" | \\"mark-broken\\" | \\"mark-fixed\\" | \\"override-failed-testing\\" | ... 11 more ... | undefined\'.", "2087876002"], @@ -331,11 +331,11 @@ exports[`stricter compilation`] = { [86, 6, 7, "Type \'false | \\"Select machines below to perform an action.\\"\' is not assignable to type \'string | undefined\'.\\n Type \'false\' is not assignable to type \'string | undefined\'.", "1236122734"], [97, 10, 16, "Argument of type \'(BaseMachine | MachineDetails | undefined)[]\' is not assignable to parameter of type \'Machine[]\'.", "4020685210"] ], - "src/app/machines/hooks.tsx:3635236711": [ + "src/app/machines/hooks.tsx:935926977": [ [45, 4, 76, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'{ aborting: Selector; abortingSelected: Selector; acquiring: Selector; ... 54 more ...; saving: (state: RootState) => boolean; }\'.\\n No index signature with a parameter of type \'string\' was found on type \'{ aborting: Selector; abortingSelected: Selector; acquiring: Selector; ... 54 more ...; saving: (state: RootState) => boolean; }\'.", "1657451879"], [45, 37, 6, "Object is possibly \'undefined\'.", "1314712411"], [45, 56, 6, "Object is possibly \'undefined\'.", "1314712411"], - [49, 11, 16, "Type \'(BaseMachine | MachineDetails | undefined)[]\' is not assignable to type \'Machine[]\'.\\n Type \'BaseMachine | MachineDetails | undefined\' is not assignable to type \'Machine\'.\\n Type \'undefined\' is not assignable to type \'Machine\'.", "2366246550"] + [50, 4, 16, "Type \'(BaseMachine | MachineDetails | undefined)[]\' is not assignable to type \'Machine[]\'.\\n Type \'BaseMachine | MachineDetails | undefined\' is not assignable to type \'Machine\'.\\n Type \'undefined\' is not assignable to type \'Machine\'.", "2366246550"] ], "src/app/machines/views/MachineDetails/MachineDetails.tsx:3514974960": [ [27, 28, 3, "Property \'get\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "193411891"], @@ -352,7 +352,7 @@ exports[`stricter compilation`] = { "src/app/machines/views/MachineDetails/MachineSummary/NumaCard/NumaCard.test.tsx:2502469861": [ [33, 27, 10, "Property \'numa_nodes\' does not exist on type \'Machine\'.\\n Property \'numa_nodes\' does not exist on type \'BaseMachine\'.", "3857696382"] ], - "src/app/machines/views/MachineDetails/MachineSummary/TestResults/TestResults.tsx:4182062532": [ + "src/app/machines/views/MachineDetails/MachineSummary/TestResults/TestResults.tsx:2985488462": [ [18, 18, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"], [37, 9, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"], [52, 15, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"], @@ -511,18 +511,18 @@ exports[`stricter compilation`] = { "src/testing/factories/notification.ts:2660496186": [ [10, 2, 5, "Type \'null\' is not assignable to type \'string | ArrayFactory | AttributeFunction | Factory | DerivedFunction\'.", "179146135"] ], - "src/testing/factories/state.ts:3687536065": [ - [232, 2, 4, "Type \'null\' is not assignable to type \'OSInfo | ArrayFactory | AttributeFunction | Factory | DerivedFunction\'.", "2087377941"], - [311, 2, 6, "Type \'null\' is not assignable to type \'string | ArrayFactory | AttributeFunction | Factory | DerivedFunction, string>\'.", "2158674347"], - [313, 2, 4, "Type \'null\' is not assignable to type \'string | ArrayFactory | AttributeFunction | Factory | DerivedFunction, string>\'.", "2087809207"], - [318, 2, 6, "Type \'null\' is not assignable to type \'ArrayFactory | \\"PUSH\\" | \\"POP\\" | \\"REPLACE\\" | AttributeFunction | Factory | DerivedFunction, Action>\'.", "1314712411"] + "src/testing/factories/state.ts:1886719305": [ + [233, 2, 4, "Type \'null\' is not assignable to type \'OSInfo | ArrayFactory | AttributeFunction | Factory | DerivedFunction\'.", "2087377941"], + [312, 2, 6, "Type \'null\' is not assignable to type \'string | ArrayFactory | AttributeFunction | Factory | DerivedFunction, string>\'.", "2158674347"], + [314, 2, 4, "Type \'null\' is not assignable to type \'string | ArrayFactory | AttributeFunction | Factory | DerivedFunction, string>\'.", "2087809207"], + [319, 2, 6, "Type \'null\' is not assignable to type \'ArrayFactory | \\"PUSH\\" | \\"POP\\" | \\"REPLACE\\" | AttributeFunction | Factory | DerivedFunction, Action>\'.", "1314712411"] ] }` }; exports[`no TSFixMe types`] = { value: `{ - "src/app/base/types.ts:2567213908": [ + "src/app/base/types.ts:1483624430": [ [0, 11, 8, "RegExp match", "1152173309"] ], "src/app/store/auth/selectors.ts:1742453516": [ diff --git a/ui/src/app/base/components/ActionForm/ActionForm.tsx b/ui/src/app/base/components/ActionForm/ActionForm.tsx index cf60020976..9806cf615a 100644 --- a/ui/src/app/base/components/ActionForm/ActionForm.tsx +++ b/ui/src/app/base/components/ActionForm/ActionForm.tsx @@ -2,7 +2,7 @@ import { Spinner, Strip } from "@canonical/react-components"; import type { ReactNode } from "react"; import React, { useState } from "react"; -import type { TSFixMe } from "app/base/types"; +import type { AnalyticsEvent, TSFixMe } from "app/base/types"; import { useProcessing } from "app/base/hooks"; import { formatErrors } from "app/utils"; import FormikForm from "app/base/components/FormikForm"; @@ -99,6 +99,7 @@ type Props = { loaded?: boolean; loading?: boolean; modelName: string; + onSaveAnalytics?: AnalyticsEvent; onSubmit: (...args: unknown[]) => void; onSuccess?: () => void; processingCount?: number; @@ -120,6 +121,7 @@ const ActionForm = ({ loaded = true, loading, modelName, + onSaveAnalytics, onSubmit, onSuccess, processingCount, @@ -154,11 +156,7 @@ const ActionForm = ({ initialValues={initialValues} loading={loading} onCancel={clearSelectedAction} - onSaveAnalytics={{ - action: actionName, - category: "Take action form", - label: getLabel(modelName, actionName), - }} + onSaveAnalytics={onSaveAnalytics} onSubmit={(...args) => { onSubmit(...args); setProcessing(true); diff --git a/ui/src/app/base/types.ts b/ui/src/app/base/types.ts index 9dc63f4efb..1c88ab6140 100644 --- a/ui/src/app/base/types.ts +++ b/ui/src/app/base/types.ts @@ -8,3 +8,9 @@ export type Sort = { export type RouteParams = { id: string; }; + +export type AnalyticsEvent = { + action: string; + category: string; + label: string; +}; diff --git a/ui/src/app/kvm/components/KVMActionFormWrapper/ComposeForm/ComposeForm.tsx b/ui/src/app/kvm/components/KVMActionFormWrapper/ComposeForm/ComposeForm.tsx index dda21666c3..029ac5a689 100644 --- a/ui/src/app/kvm/components/KVMActionFormWrapper/ComposeForm/ComposeForm.tsx +++ b/ui/src/app/kvm/components/KVMActionFormWrapper/ComposeForm/ComposeForm.tsx @@ -312,6 +312,11 @@ const ComposeForm = ({ setSelectedAction }: Props): JSX.Element => { zone: `${zones[0]?.id}` || "", }} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: "KVM details action form", + label: "Compose", + }} onSubmit={(values: ComposeFormValues) => { // Remove any errors before dispatching compose action. dispatch(cleanup()); diff --git a/ui/src/app/kvm/components/KVMActionFormWrapper/DeleteForm/DeleteForm.tsx b/ui/src/app/kvm/components/KVMActionFormWrapper/DeleteForm/DeleteForm.tsx index cf8a1c7596..63ff0a060b 100644 --- a/ui/src/app/kvm/components/KVMActionFormWrapper/DeleteForm/DeleteForm.tsx +++ b/ui/src/app/kvm/components/KVMActionFormWrapper/DeleteForm/DeleteForm.tsx @@ -14,6 +14,7 @@ type Props = { const DeleteForm = ({ setSelectedAction }: Props): JSX.Element => { const dispatch = useDispatch(); const { id } = useParams(); + const activePod = useSelector(podSelectors.active); const errors = useSelector(podSelectors.errors); const selectedKVMIDs = useSelector(podSelectors.selectedKVMs).map( (kvm) => kvm.id @@ -29,6 +30,11 @@ const DeleteForm = ({ setSelectedAction }: Props): JSX.Element => { clearSelectedAction={() => setSelectedAction(null)} errors={errors} modelName="KVM" + onSaveAnalytics={{ + action: "Submit", + category: `KVM ${activePod ? "details" : "list"} action form`, + label: "Delete", + }} onSubmit={() => { kvmsToDelete.forEach((kvmID) => { dispatch(podActions.delete(kvmID)); diff --git a/ui/src/app/kvm/components/KVMActionFormWrapper/RefreshForm/RefreshForm.tsx b/ui/src/app/kvm/components/KVMActionFormWrapper/RefreshForm/RefreshForm.tsx index 3da5970e49..db74da6926 100644 --- a/ui/src/app/kvm/components/KVMActionFormWrapper/RefreshForm/RefreshForm.tsx +++ b/ui/src/app/kvm/components/KVMActionFormWrapper/RefreshForm/RefreshForm.tsx @@ -14,6 +14,7 @@ type Props = { const RefreshForm = ({ setSelectedAction }: Props): JSX.Element | null => { const dispatch = useDispatch(); const { id } = useParams(); + const activePod = useSelector(podSelectors.active); const errors = useSelector(podSelectors.errors); const selectedKVMIDs = useSelector(podSelectors.selectedKVMs).map( (kvm) => kvm.id @@ -30,6 +31,11 @@ const RefreshForm = ({ setSelectedAction }: Props): JSX.Element | null => { clearSelectedAction={() => setSelectedAction(null)} errors={errors} modelName="KVM" + onSaveAnalytics={{ + action: "Submit", + category: `KVM ${activePod ? "details" : "list"} action form`, + label: "Refresh", + }} onSubmit={() => { kvmsToRefresh.forEach((podID) => { dispatch(podActions.refresh(podID)); diff --git a/ui/src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.test.tsx b/ui/src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.test.tsx index 4842110bad..dce22cbb1b 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.test.tsx +++ b/ui/src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.test.tsx @@ -7,16 +7,16 @@ import React from "react"; import type { RootState } from "app/store/root/types"; import { - scripts as scriptsFactory, - machineAction as machineActionFactory, - machine as machineFactory, generalState as generalStateFactory, + machine as machineFactory, + machineAction as machineActionFactory, + machineActionsState as machineActionsStateFactory, machineState as machineStateFactory, - scriptsState as scriptsStateFactory, - rootState as rootStateFactory, machineStatus as machineStatusFactory, machineStatuses as machineStatusesFactory, - machineActionsState as machineActionsStateFactory, + rootState as rootStateFactory, + scripts as scriptsFactory, + scriptsState as scriptsStateFactory, } from "testing/factories"; import CommissionForm from "./CommissionForm"; @@ -31,7 +31,7 @@ describe("CommissionForm", () => { data: [ machineActionFactory({ name: "commission", - sentence: "commission", + title: "Commission", }), ], }), diff --git a/ui/src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.tsx b/ui/src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.tsx index 671cac1577..045e8ff71b 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.tsx +++ b/ui/src/app/machines/components/ActionFormWrapper/CommissionForm/CommissionForm.tsx @@ -62,6 +62,7 @@ type Props = { export const CommissionForm = ({ setSelectedAction }: Props): JSX.Element => { const dispatch = useDispatch(); + const activeMachine = useSelector(machineSelectors.active); const errors = useSelector(machineSelectors.errors); const scriptsLoaded = useSelector(scriptSelectors.loaded); const commissioningScripts = useSelector(scriptSelectors.commissioning); @@ -119,6 +120,11 @@ export const CommissionForm = ({ setSelectedAction }: Props): JSX.Element => { }} loaded={scriptsLoaded} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: `Machine ${activeMachine ? "details" : "list"} action form`, + label: "Commission", + }} onSubmit={(values: CommissionFormValues) => { const { enableSSH, diff --git a/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.test.tsx b/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.test.tsx index e20f1b6fe3..304c7bf013 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.test.tsx +++ b/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.test.tsx @@ -13,11 +13,13 @@ import { configState as configStateFactory, defaultMinHweKernelState as defaultMinHweKerelStateFactory, generalState as generalStateFactory, - osInfoState as osInfoStateFactory, - rootState as rootStateFactory, machine as machineFactory, + machineAction as machineActionFactory, + machineActionsState as machineActionsStateFactory, machineState as machineStateFactory, machineStatus as machineStatusFactory, + osInfoState as osInfoStateFactory, + rootState as rootStateFactory, } from "testing/factories"; const mockStore = configureStore(); @@ -48,6 +50,14 @@ describe("DeployForm", () => { data: "ga-18.04", loaded: true, }), + machineActions: machineActionsStateFactory({ + data: [ + machineActionFactory({ + name: "deploy", + title: "Deploy", + }), + ], + }), osInfo: osInfoStateFactory({ data: { osystems: [ @@ -338,10 +348,13 @@ describe("DeployForm", () => { ]); }); - it("sends an event if cloud-init is set", () => { + it("sends an analytics event with cloud-init user data set", () => { const state = { ...initialState }; state.machine.selected = ["abc123"]; - const useSendMock = jest.spyOn(hooks, "useSendAnalytics"); + const mockSendAnalytics = jest.fn(); + const mockUseSendAnalytics = (hooks.useSendAnalytics = jest.fn( + () => mockSendAnalytics + )); const store = mockStore(state); const wrapper = mount( @@ -352,6 +365,7 @@ describe("DeployForm", () => { ); + act(() => wrapper.find("Formik").props().onSubmit({ includeUserData: true, @@ -362,6 +376,13 @@ describe("DeployForm", () => { userData: "test script", }) ); - expect(useSendMock).toHaveBeenCalled(); + + expect(mockSendAnalytics).toHaveBeenCalled(); + expect(mockSendAnalytics.mock.calls[0]).toEqual([ + "Machine list deploy form", + "Has cloud-init config", + "Cloud-init user data", + ]); + mockUseSendAnalytics.mockRestore(); }); }); diff --git a/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.tsx b/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.tsx index 163f4253e8..778e3b50df 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.tsx +++ b/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployForm.tsx @@ -41,6 +41,7 @@ type Props = { export const DeployForm = ({ setSelectedAction }: Props): JSX.Element => { const dispatch = useDispatch(); + const activeMachine = useSelector(machineSelectors.active); const errors = useSelector(machineSelectors.errors); const defaultMinHweKernel = useSelector( generalSelectors.defaultMinHweKernel.get @@ -52,6 +53,7 @@ export const DeployForm = ({ setSelectedAction }: Props): JSX.Element => { generalSelectors.defaultMinHweKernel.loaded ); const osInfoLoaded = useSelector(generalSelectors.osInfo.loaded); + const sendAnalytics = useSendAnalytics(); const { machinesToAction, processingCount } = useMachineActionForm("deploy"); useEffect(() => { @@ -77,8 +79,6 @@ export const DeployForm = ({ setSelectedAction }: Props): JSX.Element => { initialRelease = default_release; } - const sendAnalytics = useSendAnalytics(); - return ( { }} loaded={defaultMinHweKernelLoaded && osInfoLoaded} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: `Machine ${activeMachine ? "details" : "list"} action form`, + label: "Deploy", + }} onSubmit={(values: DeployFormValues) => { const hasUserData = values.includeUserData && values.userData && values.userData !== ""; diff --git a/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployFormFields/UserDataField/UserDataField.test.tsx b/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployFormFields/UserDataField/UserDataField.test.tsx index c8ab4a67de..4da9d94f05 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployFormFields/UserDataField/UserDataField.test.tsx +++ b/ui/src/app/machines/components/ActionFormWrapper/DeployForm/DeployFormFields/UserDataField/UserDataField.test.tsx @@ -5,7 +5,10 @@ import { MemoryRouter } from "react-router-dom"; import React from "react"; import { Provider } from "react-redux"; -import { machine as machineFactory } from "testing/factories"; +import { + generalState as generalStateFactory, + machine as machineFactory, +} from "testing/factories"; import { TSFixMe } from "app/base/types"; import DeployForm from "../../DeployForm"; @@ -61,7 +64,7 @@ describe("DeployFormFields", () => { loaded: true, loading: false, }, - general: { + general: generalStateFactory({ defaultMinHweKernel: { data: "", errors: {}, @@ -106,7 +109,7 @@ describe("DeployFormFields", () => { loaded: true, loading: false, }, - }, + }), machine: { errors: {}, loading: false, @@ -147,11 +150,7 @@ describe("DeployFormFields", () => { wrapper = mount( - + ); diff --git a/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/FieldlessForm.js b/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/FieldlessForm.js index 0f4964347e..7053b1f60a 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/FieldlessForm.js +++ b/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/FieldlessForm.js @@ -28,6 +28,7 @@ const fieldlessActions = [ export const FieldlessForm = ({ selectedAction, setSelectedAction }) => { const dispatch = useDispatch(); + const activeMachine = useSelector(machineSelectors.active); const errors = useSelector(machineSelectors.errors); const { machinesToAction, processingCount } = useMachineActionForm( selectedAction.name @@ -41,6 +42,11 @@ export const FieldlessForm = ({ selectedAction, setSelectedAction }) => { clearSelectedAction={() => setSelectedAction(null, true)} errors={errors} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: `Machine ${activeMachine ? "details" : "list"} action form`, + label: selectedAction.name, + }} onSubmit={() => { if (fieldlessActions.includes(selectedAction.name)) { const actionMethod = kebabToCamelCase(selectedAction.name); diff --git a/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/FieldlessForm.test.js b/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/FieldlessForm.test.js index 1126863e19..d267a1599b 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/FieldlessForm.test.js +++ b/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/FieldlessForm.test.js @@ -9,6 +9,8 @@ import FieldlessForm from "./FieldlessForm"; import { generalState as generalStateFactory, machine as machineFactory, + machineAction as machineActionFactory, + machineActionsState as machineActionsStateFactory, machineState as machineStateFactory, rootState as rootStateFactory, } from "testing/factories"; @@ -33,21 +35,24 @@ describe("FieldlessForm", () => { }, }), general: generalStateFactory({ - machineActions: { + machineActions: machineActionsStateFactory({ data: [ - { name: "abort", sentence: "abort" }, - { name: "acquire", sentence: "acquire" }, - { name: "delete", sentence: "delete" }, - { name: "exit-rescue-mode", sentence: "exit-rescue-mode" }, - { name: "lock", sentence: "lock" }, - { name: "mark-fixed", sentence: "mark-fixed" }, - { name: "off", sentence: "off" }, - { name: "on", sentence: "on" }, - { name: "release", sentence: "release" }, - { name: "rescue-mode", sentence: "rescue-mode" }, - { name: "unlock", sentence: "unlock" }, + machineActionFactory({ name: "abort", title: "Abort" }), + machineActionFactory({ name: "acquire", title: "Acquire" }), + machineActionFactory({ name: "delete", title: "Delete" }), + machineActionFactory({ + name: "exit-rescue-mode", + title: "Exit rescue mode", + }), + machineActionFactory({ name: "lock", title: "Lock" }), + machineActionFactory({ name: "mark-fixed", title: "Mark fixed" }), + machineActionFactory({ name: "off", title: "Power off" }), + machineActionFactory({ name: "on", title: "Power on" }), + machineActionFactory({ name: "release", title: "Release" }), + machineActionFactory({ name: "rescue-mode", title: "Rescue mode" }), + machineActionFactory({ name: "unlock", title: "Unlock" }), ], - }, + }), }), }); }); diff --git a/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/__snapshots__/FieldlessForm.test.js.snap b/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/__snapshots__/FieldlessForm.test.js.snap index addba0c8ee..a1a2303bec 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/__snapshots__/FieldlessForm.test.js.snap +++ b/ui/src/app/machines/components/ActionFormWrapper/FieldlessForm/__snapshots__/FieldlessForm.test.js.snap @@ -16,6 +16,13 @@ exports[`FieldlessForm renders 1`] = ` clearSelectedAction={[Function]} errors={Object {}} modelName="machine" + onSaveAnalytics={ + Object { + "action": "Submit", + "category": "Machine list action form", + "label": "release", + } + } onSubmit={[Function]} processingCount={0} selectedCount={0} @@ -32,9 +39,9 @@ exports[`FieldlessForm renders 1`] = ` onCancel={[Function]} onSaveAnalytics={ Object { - "action": "release", - "category": "Take action form", - "label": "Release machine", + "action": "Submit", + "category": "Machine list action form", + "label": "release", } } onSubmit={[Function]} diff --git a/ui/src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.test.tsx b/ui/src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.test.tsx index f40c997eb7..b5ead2c89c 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.test.tsx +++ b/ui/src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.test.tsx @@ -11,6 +11,7 @@ import { generalState as generalStateFactory, rootState as rootStateFactory, machine as machineFactory, + machineAction as machineActionFactory, machineActionsState as machineActionsStateFactory, machineState as machineStateFactory, machineStatus as machineStatusFactory, @@ -25,12 +26,12 @@ describe("MarkBrokenForm", () => { general: generalStateFactory({ machineActions: machineActionsStateFactory({ data: [ - { + machineActionFactory({ name: "mark-broken", title: "Mark broken", sentence: "marked broken", type: "testing", - }, + }), ], }), }), diff --git a/ui/src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.tsx b/ui/src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.tsx index 637c5b3efb..6590864c00 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.tsx +++ b/ui/src/app/machines/components/ActionFormWrapper/MarkBrokenForm/MarkBrokenForm.tsx @@ -24,6 +24,7 @@ type Props = { export const MarkBrokenForm = ({ setSelectedAction }: Props): JSX.Element => { const dispatch = useDispatch(); + const activeMachine = useSelector(machineSelectors.active); const machineErrors = useSelector(machineSelectors.errors); const errors = Object.keys(machineErrors).length > 0 ? machineErrors : null; const { machinesToAction, processingCount } = useMachineActionForm( @@ -48,6 +49,11 @@ export const MarkBrokenForm = ({ setSelectedAction }: Props): JSX.Element => { comment: "", }} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: `Machine ${activeMachine ? "details" : "list"} action form`, + label: "Mark broken", + }} onSubmit={(values: MarkBrokenFormValues) => { machinesToAction.forEach((machine) => { dispatch( diff --git a/ui/src/app/machines/components/ActionFormWrapper/OverrideTestForm/OverrideTestForm.js b/ui/src/app/machines/components/ActionFormWrapper/OverrideTestForm/OverrideTestForm.js index 8fa699a0e0..47b2bc9408 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/OverrideTestForm/OverrideTestForm.js +++ b/ui/src/app/machines/components/ActionFormWrapper/OverrideTestForm/OverrideTestForm.js @@ -59,6 +59,7 @@ const OverrideTestFormSchema = Yup.object().shape({ export const OverrideTestForm = ({ setSelectedAction }) => { const dispatch = useDispatch(); + const activeMachine = useSelector(machineSelectors.active); const errors = useSelector(machineSelectors.errors); const scriptResultsLoaded = useSelector(scriptresultsSelectors.loaded); const { machinesToAction, processingCount } = useMachineActionForm( @@ -88,6 +89,11 @@ export const OverrideTestForm = ({ setSelectedAction }) => { loaded={scriptResultsLoaded} loading={!scriptResultsLoaded} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: `Machine ${activeMachine ? "details" : "list"} action form`, + label: "Override failed tests", + }} onSubmit={(values) => { const { suppressResults } = values; machinesToAction.forEach((machine) => { diff --git a/ui/src/app/machines/components/ActionFormWrapper/SetPoolForm/SetPoolForm.js b/ui/src/app/machines/components/ActionFormWrapper/SetPoolForm/SetPoolForm.js index f9fcbbb8c3..19448fb2dd 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/SetPoolForm/SetPoolForm.js +++ b/ui/src/app/machines/components/ActionFormWrapper/SetPoolForm/SetPoolForm.js @@ -4,6 +4,7 @@ import PropTypes from "prop-types"; import React, { useEffect, useState } from "react"; import { machine as machineActions } from "app/base/actions"; +import { useSendAnalytics } from "app/base/hooks"; import { useMachineActionForm } from "app/machines/hooks"; import machineSelectors from "app/store/machine/selectors"; import { actions as resourcePoolActions } from "app/store/resourcepool"; @@ -24,6 +25,7 @@ export const SetPoolForm = ({ setSelectedAction }) => { description: "", name: "", }); + const activeMachine = useSelector(machineSelectors.active); const machineErrors = useSelector(machineSelectors.errors); const poolErrors = useSelector(resourcePoolSelectors.errors); const resourcePools = useSelector(resourcePoolSelectors.all); @@ -55,6 +57,11 @@ export const SetPoolForm = ({ setSelectedAction }) => { initialValues={initialValues} loaded={resourcePoolsLoaded} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: `Machine ${activeMachine ? "details" : "list"} action form`, + label: "Set pool", + }} onSubmit={(values) => { if (values.poolSelection === "create") { dispatch( diff --git a/ui/src/app/machines/components/ActionFormWrapper/SetPoolForm/SetPoolForm.test.js b/ui/src/app/machines/components/ActionFormWrapper/SetPoolForm/SetPoolForm.test.js index 3f385840d7..9642d66cbc 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/SetPoolForm/SetPoolForm.test.js +++ b/ui/src/app/machines/components/ActionFormWrapper/SetPoolForm/SetPoolForm.test.js @@ -8,6 +8,8 @@ import React from "react"; import SetPoolForm from "./SetPoolForm"; import { machine as machineFactory, + machineAction as machineActionFactory, + machineActionsState as machineActionsStateFactory, machineState as machineStateFactory, generalState as generalStateFactory, resourcePool as resourcePoolFactory, @@ -22,9 +24,15 @@ describe("SetPoolForm", () => { beforeEach(() => { state = rootStateFactory({ general: generalStateFactory({ - machineActions: { - data: [{ name: "set-pool", sentence: "change those pools" }], - }, + machineActions: machineActionsStateFactory({ + data: [ + machineActionFactory({ + name: "set-pool", + sentence: "change those pools", + title: "Set pool", + }), + ], + }), }), machine: machineStateFactory({ errors: {}, diff --git a/ui/src/app/machines/components/ActionFormWrapper/SetZoneForm/SetZoneForm.js b/ui/src/app/machines/components/ActionFormWrapper/SetZoneForm/SetZoneForm.js index 324088755a..e23491d122 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/SetZoneForm/SetZoneForm.js +++ b/ui/src/app/machines/components/ActionFormWrapper/SetZoneForm/SetZoneForm.js @@ -18,6 +18,7 @@ const SetZoneSchema = Yup.object().shape({ export const SetZoneForm = ({ setSelectedAction }) => { const dispatch = useDispatch(); + const activeMachine = useSelector(machineSelectors.active); const errors = useSelector(machineSelectors.errors); const zones = useSelector(zoneSelectors.all); const zonesLoaded = useSelector(zoneSelectors.loaded); @@ -47,6 +48,11 @@ export const SetZoneForm = ({ setSelectedAction }) => { initialValues={{ zone: "" }} loaded={zonesLoaded} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: `Machine ${activeMachine ? "details" : "list"} action form`, + label: "Set zone", + }} onSubmit={(values) => { const zone = zones.find((zone) => zone.name === values.zone); machinesToAction.forEach((machine) => { diff --git a/ui/src/app/machines/components/ActionFormWrapper/SetZoneForm/SetZoneForm.test.js b/ui/src/app/machines/components/ActionFormWrapper/SetZoneForm/SetZoneForm.test.js index 1fa6362b5f..14ccded374 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/SetZoneForm/SetZoneForm.test.js +++ b/ui/src/app/machines/components/ActionFormWrapper/SetZoneForm/SetZoneForm.test.js @@ -7,9 +7,11 @@ import React from "react"; import SetZoneForm from "./SetZoneForm"; import { + generalState as generalStateFactory, machine as machineFactory, + machineAction as machineActionFactory, + machineActionsState as machineActionsStateFactory, machineState as machineStateFactory, - generalState as generalStateFactory, rootState as rootStateFactory, zone as zoneFactory, zoneState as zoneStateFactory, @@ -22,9 +24,9 @@ describe("SetZoneForm", () => { beforeEach(() => { state = rootStateFactory({ general: generalStateFactory({ - machineActions: { - data: [{ name: "set-zone", sentence: "set-zone" }], - }, + machineActions: machineActionsStateFactory({ + data: [machineActionFactory({ name: "set-zone", title: "Set zone" })], + }), }), machine: machineStateFactory({ errors: {}, diff --git a/ui/src/app/machines/components/ActionFormWrapper/TagForm/TagForm.js b/ui/src/app/machines/components/ActionFormWrapper/TagForm/TagForm.js index c15cad1931..c2a58fb030 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/TagForm/TagForm.js +++ b/ui/src/app/machines/components/ActionFormWrapper/TagForm/TagForm.js @@ -25,6 +25,7 @@ const TagFormSchema = Yup.object().shape({ export const TagForm = ({ setSelectedAction }) => { const dispatch = useDispatch(); + const activeMachine = useSelector(machineSelectors.active); const [initialValues, setInitialValues] = useState({ tags: [] }); const errors = useSelector(machineSelectors.errors); const tagsLoaded = useSelector(tagSelectors.loaded); @@ -49,6 +50,11 @@ export const TagForm = ({ setSelectedAction }) => { initialValues={initialValues} loaded={tagsLoaded} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: `Machine ${activeMachine ? "details" : "list"} action form`, + label: "Tag", + }} onSubmit={(values) => { if (values.tags && values.tags.length) { machinesToAction.forEach((machine) => { diff --git a/ui/src/app/machines/components/ActionFormWrapper/TagForm/TagForm.test.js b/ui/src/app/machines/components/ActionFormWrapper/TagForm/TagForm.test.js index 89aff55a44..eff11c38d2 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/TagForm/TagForm.test.js +++ b/ui/src/app/machines/components/ActionFormWrapper/TagForm/TagForm.test.js @@ -9,6 +9,8 @@ import TagForm from "./TagForm"; import { generalState as generalStateFactory, machine as machineFactory, + machineAction as machineActionFactory, + machineActionsState as machineActionsStateFactory, machineState as machineStateFactory, rootState as rootStateFactory, tagState as tagStateFactory, @@ -22,9 +24,9 @@ describe("TagForm", () => { beforeEach(() => { initialState = rootStateFactory({ general: generalStateFactory({ - machineActions: { - data: [{ name: "tag", sentence: "tag" }], - }, + machineActions: machineActionsStateFactory({ + data: [machineActionFactory({ name: "tag", title: "Tag" })], + }), }), machine: machineStateFactory({ loaded: true, diff --git a/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx b/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx index 50ab36e3bb..aa0cbb3a6a 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx +++ b/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx @@ -10,11 +10,12 @@ import { HardwareType } from "app/base/enum"; import { generalState as generalStateFactory, machine as machineFactory, - scripts as scriptsFactory, + machineAction as machineActionFactory, machineActionsState as machineActionsStateFactory, machineState as machineStateFactory, machineStatus as machineStatusFactory, rootState as rootStateFactory, + scripts as scriptsFactory, scriptsState as scriptsStateFactory, } from "testing/factories"; import { ScriptType } from "testing/factories/scripts"; @@ -30,9 +31,7 @@ describe("TestForm", () => { initialState = rootStateFactory({ general: generalStateFactory({ machineActions: machineActionsStateFactory({ - data: [ - { name: "test", sentence: "test", type: "test", title: "test" }, - ], + data: [machineActionFactory({ name: "test", title: "Test" })], }), }), machine: machineStateFactory({ diff --git a/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.tsx b/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.tsx index c44efd4fa7..40990fc16a 100644 --- a/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.tsx +++ b/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.tsx @@ -49,6 +49,7 @@ export const TestForm = ({ hardwareType, }: Props): JSX.Element => { const dispatch = useDispatch(); + const activeMachine = useSelector(machineSelectors.active); const errors = useSelector(machineSelectors.errors); const scripts = useSelector(scriptSelectors.testing); const scriptsLoaded = useSelector(scriptSelectors.loaded); @@ -97,6 +98,11 @@ export const TestForm = ({ }} loaded={scriptsLoaded} modelName="machine" + onSaveAnalytics={{ + action: "Submit", + category: `Machine ${activeMachine ? "details" : "list"} action form`, + label: "Test", + }} onSubmit={(values: FormValues) => { const { enableSSH, scripts, scriptInputs } = values; machinesToAction.forEach((machine) => { diff --git a/ui/src/app/machines/views/MachineDetails/MachineSummary/TestResults/TestResults.tsx b/ui/src/app/machines/views/MachineDetails/MachineSummary/TestResults/TestResults.tsx index fd0cba8424..7088420de7 100644 --- a/ui/src/app/machines/views/MachineDetails/MachineSummary/TestResults/TestResults.tsx +++ b/ui/src/app/machines/views/MachineDetails/MachineSummary/TestResults/TestResults.tsx @@ -44,7 +44,7 @@ const TestResults = ({ onClick={() => sendAnalytics( "Machine details", - "Storage tests passed link", + `${capitaliseFirst(scriptType)} tests passed link`, "Machine summary tab" ) } diff --git a/ui/src/testing/factories/state.ts b/ui/src/testing/factories/state.ts index a74f8edc58..85400293a8 100644 --- a/ui/src/testing/factories/state.ts +++ b/ui/src/testing/factories/state.ts @@ -221,6 +221,7 @@ export const knownArchitecturesState = define({ export const machineActionsState = define({ ...defaultGeneralState, + data: () => [], }); export const navigationOptionsState = define({