From 26bc545919135ceaab0fffa842b632f6e1ed8590 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Wed, 29 Jun 2022 14:17:53 -0500 Subject: [PATCH 1/5] convert to ts --- ...og.js => HeartbeatMonitorCreateDialog.tsx} | 20 +++++++++---------- web/src/app/services/HeartbeatMonitorForm.tsx | 13 ++++++------ web/src/app/util/errutil.ts | 5 +++-- 3 files changed, 19 insertions(+), 19 deletions(-) rename web/src/app/services/{HeartbeatMonitorCreateDialog.js => HeartbeatMonitorCreateDialog.tsx} (71%) diff --git a/web/src/app/services/HeartbeatMonitorCreateDialog.js b/web/src/app/services/HeartbeatMonitorCreateDialog.tsx similarity index 71% rename from web/src/app/services/HeartbeatMonitorCreateDialog.js rename to web/src/app/services/HeartbeatMonitorCreateDialog.tsx index f888a2546e..c39a3d4fba 100644 --- a/web/src/app/services/HeartbeatMonitorCreateDialog.js +++ b/web/src/app/services/HeartbeatMonitorCreateDialog.tsx @@ -1,10 +1,10 @@ import React, { useState } from 'react' -import { useMutation, gql } from '@apollo/client' -import p from 'prop-types' +import { gql } from 'urql' +import { useMutation } from '@apollo/client' import { fieldErrors, nonFieldErrors } from '../util/errutil' import FormDialog from '../dialogs/FormDialog' -import HeartbeatMonitorForm from './HeartbeatMonitorForm' +import HeartbeatMonitorForm, { Value } from './HeartbeatMonitorForm' const createMutation = gql` mutation ($input: CreateHeartbeatMonitorInput!) { @@ -14,8 +14,11 @@ const createMutation = gql` } ` -export default function HeartbeatMonitorCreateDialog(props) { - const [value, setValue] = useState({ name: '', timeoutMinutes: 15 }) +export default function HeartbeatMonitorCreateDialog(props: { + serviceID: string + onClose: () => void +}): JSX.Element { + const [value, setValue] = useState({ name: '', timeoutMinutes: 15 }) const [createHeartbeat, { loading, error }] = useMutation(createMutation, { variables: { input: { @@ -42,14 +45,9 @@ export default function HeartbeatMonitorCreateDialog(props) { }))} disabled={loading} value={value} - onChange={(value) => setValue(value)} + onChange={(value: Value) => setValue(value)} /> } /> ) } - -HeartbeatMonitorCreateDialog.propTypes = { - serviceID: p.string.isRequired, - onClose: p.func, -} diff --git a/web/src/app/services/HeartbeatMonitorForm.tsx b/web/src/app/services/HeartbeatMonitorForm.tsx index a528b595df..6385646580 100644 --- a/web/src/app/services/HeartbeatMonitorForm.tsx +++ b/web/src/app/services/HeartbeatMonitorForm.tsx @@ -3,6 +3,7 @@ import Grid from '@mui/material/Grid' import TextField from '@mui/material/TextField' import { FormContainer, FormField } from '../forms' import NumberField from '../util/NumberField' +import { FieldError } from '../util/errutil' function clampTimeout(val: string): number | string { if (!val) return '' @@ -12,19 +13,19 @@ function clampTimeout(val: string): number | string { // need to have the min be 1 here so you can type `10` return Math.min(Math.max(1, num), 9000) } -interface Value { +export interface Value { name: string - timeoutMinutes: [number, string] + timeoutMinutes: [number, string] | number } interface HeartbeatMonitorFormProps { value: Value - errors: { - field: 'name' | 'timeoutMinutes' - message: string - }[] + errors: FieldError[] onChange: (val: Value) => void + + // can be deleted when FormContainer.js is converted to ts + disabled: boolean } export default function HeartbeatMonitorForm( diff --git a/web/src/app/util/errutil.ts b/web/src/app/util/errutil.ts index 5cff4ed93a..288a8fffb0 100644 --- a/web/src/app/util/errutil.ts +++ b/web/src/app/util/errutil.ts @@ -1,6 +1,7 @@ import _ from 'lodash' import { ApolloError } from '@apollo/client' import { GraphQLError } from 'graphql/error' +import { CombinedError } from 'urql' const mapName = (name: string): string => _.camelCase(name).replace(/Id$/, 'ID') @@ -22,7 +23,7 @@ const parseDetails = (msg: string): { [x: string]: string } => { // nonFieldErrors will return a flat list of non-field errors (if any) from a graphQL error. // // All returned errors should have a `message` property. -export function nonFieldErrors(err?: ApolloError): Error[] { +export function nonFieldErrors(err?: ApolloError | CombinedError): Error[] { if (!err) return [] if (!err.graphQLErrors || !err.graphQLErrors.length) return [err] @@ -49,7 +50,7 @@ interface RawFieldError extends Error { // fieldErrors will return a flat list of field errors (if any) from a graphQL error. // // All returned errors will be of the format {field, message} -export function fieldErrors(err?: ApolloError): FieldError[] { +export function fieldErrors(err?: ApolloError | CombinedError): FieldError[] { if (!err) return [] if (!err.graphQLErrors) return [] From ec762f4490c40a50cd5573a028a1244fdd8b526d Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Wed, 29 Jun 2022 16:53:31 -0500 Subject: [PATCH 2/5] use urql --- .../services/HeartbeatMonitorCreateDialog.tsx | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/web/src/app/services/HeartbeatMonitorCreateDialog.tsx b/web/src/app/services/HeartbeatMonitorCreateDialog.tsx index c39a3d4fba..044335ecf4 100644 --- a/web/src/app/services/HeartbeatMonitorCreateDialog.tsx +++ b/web/src/app/services/HeartbeatMonitorCreateDialog.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react' -import { gql } from 'urql' -import { useMutation } from '@apollo/client' +import { gql, useMutation } from 'urql' import { fieldErrors, nonFieldErrors } from '../util/errutil' import FormDialog from '../dialogs/FormDialog' @@ -19,31 +18,34 @@ export default function HeartbeatMonitorCreateDialog(props: { onClose: () => void }): JSX.Element { const [value, setValue] = useState({ name: '', timeoutMinutes: 15 }) - const [createHeartbeat, { loading, error }] = useMutation(createMutation, { - variables: { - input: { - name: value.name, - timeoutMinutes: value.timeoutMinutes, - serviceID: props.serviceID, - }, - }, - }) + const [createHeartbeatStatus, createHeartbeat] = useMutation(createMutation) return ( createHeartbeat().then(props.onClose)} + onSubmit={() => + createHeartbeat( + { + input: { + name: value.name, + timeoutMinutes: value.timeoutMinutes, + serviceID: props.serviceID, + }, + }, + { additionalTypenames: ['HeartbeatMonitor'] }, + ).then(props.onClose) + } form={ ({ + errors={fieldErrors(createHeartbeatStatus.error).map((f) => ({ ...f, field: f.field === 'timeout' ? 'timeoutMinutes' : f.field, }))} - disabled={loading} + disabled={createHeartbeatStatus.fetching} value={value} onChange={(value: Value) => setValue(value)} /> From 8af277dd14c659665a9f63ffd9c130a3d77caa74 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Tue, 5 Jul 2022 13:31:23 -0500 Subject: [PATCH 3/5] fix useMutation error and fetching status --- .../services/HeartbeatMonitorCreateDialog.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/web/src/app/services/HeartbeatMonitorCreateDialog.tsx b/web/src/app/services/HeartbeatMonitorCreateDialog.tsx index 044335ecf4..7afa0504b9 100644 --- a/web/src/app/services/HeartbeatMonitorCreateDialog.tsx +++ b/web/src/app/services/HeartbeatMonitorCreateDialog.tsx @@ -18,14 +18,14 @@ export default function HeartbeatMonitorCreateDialog(props: { onClose: () => void }): JSX.Element { const [value, setValue] = useState({ name: '', timeoutMinutes: 15 }) - const [createHeartbeatStatus, createHeartbeat] = useMutation(createMutation) + const [{ error, fetching }, createHeartbeat] = useMutation(createMutation) return ( createHeartbeat( @@ -37,15 +37,19 @@ export default function HeartbeatMonitorCreateDialog(props: { }, }, { additionalTypenames: ['HeartbeatMonitor'] }, - ).then(props.onClose) + ) + .then((result) => { + console.log(result) + }) + .then(props.onClose) } form={ ({ + errors={fieldErrors(error).map((f) => ({ ...f, field: f.field === 'timeout' ? 'timeoutMinutes' : f.field, }))} - disabled={createHeartbeatStatus.fetching} + disabled={fetching} value={value} onChange={(value: Value) => setValue(value)} /> From f6fb61fefa9492f2cac1df50427eb055fe1e2531 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Wed, 6 Jul 2022 13:44:29 -0500 Subject: [PATCH 4/5] resolve form validation issue --- .../services/HeartbeatMonitorCreateDialog.tsx | 20 +++++++++---------- web/src/cypress/integration/services.ts | 7 ++++++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/web/src/app/services/HeartbeatMonitorCreateDialog.tsx b/web/src/app/services/HeartbeatMonitorCreateDialog.tsx index 7afa0504b9..8aaa0d7115 100644 --- a/web/src/app/services/HeartbeatMonitorCreateDialog.tsx +++ b/web/src/app/services/HeartbeatMonitorCreateDialog.tsx @@ -18,14 +18,14 @@ export default function HeartbeatMonitorCreateDialog(props: { onClose: () => void }): JSX.Element { const [value, setValue] = useState({ name: '', timeoutMinutes: 15 }) - const [{ error, fetching }, createHeartbeat] = useMutation(createMutation) + const [createHeartbeatStatus, createHeartbeat] = useMutation(createMutation) return ( createHeartbeat( @@ -37,19 +37,19 @@ export default function HeartbeatMonitorCreateDialog(props: { }, }, { additionalTypenames: ['HeartbeatMonitor'] }, - ) - .then((result) => { - console.log(result) - }) - .then(props.onClose) + ).then((result) => { + if (!result.error) { + props.onClose() + } + }) } form={ ({ + errors={fieldErrors(createHeartbeatStatus.error).map((f) => ({ ...f, field: f.field === 'timeout' ? 'timeoutMinutes' : f.field, }))} - disabled={fetching} + disabled={createHeartbeatStatus.fetching} value={value} onChange={(value: Value) => setValue(value)} /> diff --git a/web/src/cypress/integration/services.ts b/web/src/cypress/integration/services.ts index ee4f5f2828..cbab762300 100644 --- a/web/src/cypress/integration/services.ts +++ b/web/src/cypress/integration/services.ts @@ -352,11 +352,16 @@ function testServices(screen: ScreenFormat): void { it('should create a monitor', () => { const name = c.word({ length: 5 }) + ' Monitor' const timeoutMinutes = (Math.trunc(Math.random() * 10) + 5).toString() + const invalidName = 'a' cy.pageFab() + cy.dialogForm({ name: invalidName, timeoutMinutes }) + cy.dialogClick('Submit') + cy.get('body').should('contain', 'Must be at least 2 characters') + cy.dialogForm({ name, timeoutMinutes }) - cy.dialogFinish('Submit') + cy.dialogFinish('Retry') cy.get('li').should('contain', name).should('contain', timeoutMinutes) }) From 04d266d7fc71a008c757cb22945b8d2237ca4e70 Mon Sep 17 00:00:00 2001 From: tony-tvu Date: Wed, 20 Jul 2022 09:19:41 -0500 Subject: [PATCH 5/5] make number type --- web/src/app/services/HeartbeatMonitorForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/services/HeartbeatMonitorForm.tsx b/web/src/app/services/HeartbeatMonitorForm.tsx index 6385646580..f785cf2fd6 100644 --- a/web/src/app/services/HeartbeatMonitorForm.tsx +++ b/web/src/app/services/HeartbeatMonitorForm.tsx @@ -15,7 +15,7 @@ function clampTimeout(val: string): number | string { } export interface Value { name: string - timeoutMinutes: [number, string] | number + timeoutMinutes: number } interface HeartbeatMonitorFormProps { value: Value