From 5b6e68c731ba3ebb50c031d363882968422258f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:01:44 -0800 Subject: [PATCH 01/18] build(deps): bump golang.org/x/tools from 0.16.1 to 0.17.0 (#3598) Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.16.1 to 0.17.0. - [Release notes](https://github.com/golang/tools/releases) - [Commits](https://github.com/golang/tools/compare/v0.16.1...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/tools dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a774183fa0..7c781448d1 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( golang.org/x/oauth2 v0.16.0 golang.org/x/sys v0.16.0 golang.org/x/term v0.16.0 - golang.org/x/tools v0.16.1 + golang.org/x/tools v0.17.0 google.golang.org/grpc v1.60.1 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 google.golang.org/protobuf v1.32.0 @@ -151,7 +151,7 @@ require ( golang.org/x/exp/typeparams v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/net v0.20.0 // indirect - golang.org/x/sync v0.5.0 // indirect + golang.org/x/sync v0.6.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect diff --git a/go.sum b/go.sum index 3273ca927d..431dae711a 100644 --- a/go.sum +++ b/go.sum @@ -1447,8 +1447,8 @@ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1641,8 +1641,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From afe2546c8f2fcd81ffef23b1b1ee65619d216c44 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:03:00 -0800 Subject: [PATCH 02/18] build(deps-dev): bump @typescript-eslint/parser from 6.15.0 to 6.18.1 (#3594) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 6.15.0 to 6.18.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.18.1/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 65 ++++++++-------------------------------------------- 2 files changed, 11 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index 9081cda152..5b6eee5e30 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "@types/react-transition-group": "4.4.7", "@types/react-virtualized-auto-sizer": "1.0.4", "@typescript-eslint/eslint-plugin": "6.18.1", - "@typescript-eslint/parser": "6.15.0", + "@typescript-eslint/parser": "6.18.1", "@urql/exchange-retry": "1.2.0", "bowser": "2.11.0", "chance": "1.1.11", diff --git a/yarn.lock b/yarn.lock index 6276ffc4f1..61bdbbae40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6228,21 +6228,21 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:6.15.0": - version: 6.15.0 - resolution: "@typescript-eslint/parser@npm:6.15.0" - dependencies: - "@typescript-eslint/scope-manager": 6.15.0 - "@typescript-eslint/types": 6.15.0 - "@typescript-eslint/typescript-estree": 6.15.0 - "@typescript-eslint/visitor-keys": 6.15.0 +"@typescript-eslint/parser@npm:6.18.1": + version: 6.18.1 + resolution: "@typescript-eslint/parser@npm:6.18.1" + dependencies: + "@typescript-eslint/scope-manager": 6.18.1 + "@typescript-eslint/types": 6.18.1 + "@typescript-eslint/typescript-estree": 6.18.1 + "@typescript-eslint/visitor-keys": 6.18.1 debug: ^4.3.4 peerDependencies: eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 6f71b48f208e4d56025cbe3a5b287fe9c31484469e8b2a14a0ab5453cb56223a3c099beb70d298e0ce80de8a23e90aec65865ff8e939233cd0f1c3ffba12f3db + checksum: f123310976a73d9f08470dbad917c9e7b038e9e1362924a225a29d35fac1a2726d447952ca77b914d47f50791d235bb66f5171c7a4a0536e9c170fb20e73a2e4 languageName: node linkType: hard @@ -6256,16 +6256,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:6.15.0": - version: 6.15.0 - resolution: "@typescript-eslint/scope-manager@npm:6.15.0" - dependencies: - "@typescript-eslint/types": 6.15.0 - "@typescript-eslint/visitor-keys": 6.15.0 - checksum: 12316149aae3ad5c7e3411ed7da7fb7d9324df83482d64a93eecbd11063451660cea0fa42ceb026984df7974d770d5f7bc6c77c33e95bc0db0c44e4413f8b756 - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/scope-manager@npm:6.18.1" @@ -6300,13 +6290,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:6.15.0": - version: 6.15.0 - resolution: "@typescript-eslint/types@npm:6.15.0" - checksum: 604cf287a339a55c9a82a6e301cf353bb256427b6e29b12ee8901b37d34581761a0dac3ae7e9d78925854e260e5d690ec472b54ca972339820f3db8512864875 - languageName: node - linkType: hard - "@typescript-eslint/types@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/types@npm:6.18.1" @@ -6332,24 +6315,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:6.15.0": - version: 6.15.0 - resolution: "@typescript-eslint/typescript-estree@npm:6.15.0" - dependencies: - "@typescript-eslint/types": 6.15.0 - "@typescript-eslint/visitor-keys": 6.15.0 - debug: ^4.3.4 - globby: ^11.1.0 - is-glob: ^4.0.3 - semver: ^7.5.4 - ts-api-utils: ^1.0.1 - peerDependenciesMeta: - typescript: - optional: true - checksum: fbd11a5acaee3166174fad4cc78cff2ad646411a60ca14e5a50598373302c7bedd76d073ed385b002eb3d6d2a44aea2dd5c74aa65fbef8441a2e079064e67640 - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/typescript-estree@npm:6.18.1" @@ -6414,16 +6379,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:6.15.0": - version: 6.15.0 - resolution: "@typescript-eslint/visitor-keys@npm:6.15.0" - dependencies: - "@typescript-eslint/types": 6.15.0 - eslint-visitor-keys: ^3.4.1 - checksum: 1bccc4d4eea6fd10a4ab1daa9e1aaaf790d5f4dd5d02c6e3eb6e83414c086d8d5f14ac44c9fb587b2f7e0dad3e7aeae603158d89dec9ae89652024331bb84fea - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:6.18.1": version: 6.18.1 resolution: "@typescript-eslint/visitor-keys@npm:6.18.1" @@ -17550,7 +17505,7 @@ __metadata: "@types/react-transition-group": 4.4.7 "@types/react-virtualized-auto-sizer": 1.0.4 "@typescript-eslint/eslint-plugin": 6.18.1 - "@typescript-eslint/parser": 6.15.0 + "@typescript-eslint/parser": 6.18.1 "@urql/exchange-retry": 1.2.0 bowser: 2.11.0 chance: 1.1.11 From 89942203939f4228e2b7ff497d8e24a2e885a592 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 17 Jan 2024 09:27:51 -0600 Subject: [PATCH 03/18] config: Add option to auto-close acked alerts (#3604) * add new auto close cfg value * close acked if configured --- config/config.go | 9 +++++---- engine/cleanupmanager/db.go | 6 +++--- engine/cleanupmanager/update.go | 2 +- graphql2/mapconfig.go | 8 ++++++++ web/src/schema.d.ts | 1 + 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/config/config.go b/config/config.go index 3b917066ac..327a8e58bc 100644 --- a/config/config.go +++ b/config/config.go @@ -39,10 +39,11 @@ type Config struct { } Maintenance struct { - AlertCleanupDays int `public:"true" info:"Closed alerts will be deleted after this many days (0 means disable cleanup)."` - AlertAutoCloseDays int `public:"true" info:"Unacknowledged alerts will automatically be closed after this many days of inactivity. (0 means disable auto-close)."` - APIKeyExpireDays int `public:"true" info:"Unused calendar API keys will be disabled after this many days (0 means disable cleanup)."` - ScheduleCleanupDays int `public:"true" info:"Schedule on-call history will be deleted after this many days (0 means disable cleanup)."` + AlertCleanupDays int `public:"true" info:"Closed alerts will be deleted after this many days (0 means disable cleanup)."` + AlertAutoCloseDays int `public:"true" info:"Unacknowledged alerts will automatically be closed after this many days of inactivity. (0 means disable auto-close)."` + AutoCloseAckedAlerts bool `public:"true" info:"If set, alerts that are acknowledged will also be automatically closed after the configured number of days of inactivity."` + APIKeyExpireDays int `public:"true" info:"Unused calendar API keys will be disabled after this many days (0 means disable cleanup)."` + ScheduleCleanupDays int `public:"true" info:"Schedule on-call history will be deleted after this many days (0 means disable cleanup)."` } Auth struct { diff --git a/engine/cleanupmanager/db.go b/engine/cleanupmanager/db.go index 7dd00cb334..317194ac41 100644 --- a/engine/cleanupmanager/db.go +++ b/engine/cleanupmanager/db.go @@ -31,7 +31,7 @@ type DB struct { cleanupOverrides *sql.Stmt cleanupSchedOnCall *sql.Stmt cleanupEPOnCall *sql.Stmt - unackAlerts *sql.Stmt + staleAlerts *sql.Stmt alertStore *alert.Store logIndex int @@ -94,10 +94,10 @@ func NewDB(ctx context.Context, db *sql.DB, alertstore *alert.Store) (*DB, error cleanupOverrides: p.P(`DELETE FROM user_overrides WHERE id = ANY(SELECT id FROM user_overrides WHERE end_time < (now() - $1::interval) LIMIT 100 FOR UPDATE SKIP LOCKED)`), cleanupSchedOnCall: p.P(`DELETE FROM schedule_on_call_users WHERE id = ANY(SELECT id FROM schedule_on_call_users WHERE end_time < (now() - $1::interval) LIMIT 100 FOR UPDATE SKIP LOCKED)`), cleanupEPOnCall: p.P(`DELETE FROM ep_step_on_call_users WHERE id = ANY(SELECT id FROM ep_step_on_call_users WHERE end_time < (now() - $1::interval) LIMIT 100 FOR UPDATE SKIP LOCKED)`), - unackAlerts: p.P(` + staleAlerts: p.P(` select id from alerts a where - a.status='triggered' and + (a.status='triggered' or ($2 and a.status = 'active')) and created_at <= now() - '1 day'::interval * $1 and not exists ( select 1 from alert_logs log diff --git a/engine/cleanupmanager/update.go b/engine/cleanupmanager/update.go index ea86ba2741..cb5518065c 100644 --- a/engine/cleanupmanager/update.go +++ b/engine/cleanupmanager/update.go @@ -66,7 +66,7 @@ func (db *DB) update(ctx context.Context) error { } if cfg.Maintenance.AlertAutoCloseDays > 0 { - rows, err := tx.StmtContext(ctx, db.unackAlerts).QueryContext(ctx, cfg.Maintenance.AlertAutoCloseDays) + rows, err := tx.StmtContext(ctx, db.staleAlerts).QueryContext(ctx, cfg.Maintenance.AlertAutoCloseDays, cfg.Maintenance.AutoCloseAckedAlerts) if err != nil { return fmt.Errorf("query auto-close alerts: %w", err) } diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index 746aacf7ea..150d206df2 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -37,6 +37,7 @@ func MapConfigValues(cfg config.Config) []ConfigValue { {ID: "Services.RequiredLabels", Type: ConfigTypeStringList, Description: "List of label names to require new services to define.", Value: strings.Join(cfg.Services.RequiredLabels, "\n")}, {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, {ID: "Maintenance.AlertAutoCloseDays", Type: ConfigTypeInteger, Description: "Unacknowledged alerts will automatically be closed after this many days of inactivity. (0 means disable auto-close).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertAutoCloseDays)}, + {ID: "Maintenance.AutoCloseAckedAlerts", Type: ConfigTypeBoolean, Description: "If set, alerts that are acknowledged will also be automatically closed after the configured number of days of inactivity.", Value: fmt.Sprintf("%t", cfg.Maintenance.AutoCloseAckedAlerts)}, {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, {ID: "Maintenance.ScheduleCleanupDays", Type: ConfigTypeInteger, Description: "Schedule on-call history will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.ScheduleCleanupDays)}, {ID: "Auth.RefererURLs", Type: ConfigTypeStringList, Description: "Allowed referer URLs for auth and redirects.", Value: strings.Join(cfg.Auth.RefererURLs, "\n"), Deprecated: "Use --public-url flag instead, which takes precedence."}, @@ -107,6 +108,7 @@ func MapPublicConfigValues(cfg config.Config) []ConfigValue { {ID: "Services.RequiredLabels", Type: ConfigTypeStringList, Description: "List of label names to require new services to define.", Value: strings.Join(cfg.Services.RequiredLabels, "\n")}, {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, {ID: "Maintenance.AlertAutoCloseDays", Type: ConfigTypeInteger, Description: "Unacknowledged alerts will automatically be closed after this many days of inactivity. (0 means disable auto-close).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertAutoCloseDays)}, + {ID: "Maintenance.AutoCloseAckedAlerts", Type: ConfigTypeBoolean, Description: "If set, alerts that are acknowledged will also be automatically closed after the configured number of days of inactivity.", Value: fmt.Sprintf("%t", cfg.Maintenance.AutoCloseAckedAlerts)}, {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, {ID: "Maintenance.ScheduleCleanupDays", Type: ConfigTypeInteger, Description: "Schedule on-call history will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.ScheduleCleanupDays)}, {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, @@ -204,6 +206,12 @@ func ApplyConfigValues(cfg config.Config, vals []ConfigValueInput) (config.Confi return cfg, err } cfg.Maintenance.AlertAutoCloseDays = val + case "Maintenance.AutoCloseAckedAlerts": + val, err := parseBool(v.ID, v.Value) + if err != nil { + return cfg, err + } + cfg.Maintenance.AutoCloseAckedAlerts = val case "Maintenance.APIKeyExpireDays": val, err := parseInt(v.ID, v.Value) if err != nil { diff --git a/web/src/schema.d.ts b/web/src/schema.d.ts index 3fc5506268..868d2bfcb7 100644 --- a/web/src/schema.d.ts +++ b/web/src/schema.d.ts @@ -1400,6 +1400,7 @@ type ConfigID = | 'Services.RequiredLabels' | 'Maintenance.AlertCleanupDays' | 'Maintenance.AlertAutoCloseDays' + | 'Maintenance.AutoCloseAckedAlerts' | 'Maintenance.APIKeyExpireDays' | 'Maintenance.ScheduleCleanupDays' | 'Auth.RefererURLs' From 15a4ef6bc19a229c236e5b3daffd568553760f2c Mon Sep 17 00:00:00 2001 From: Tony Vu Date: Fri, 19 Jan 2024 09:16:19 -0600 Subject: [PATCH 04/18] add assertion for editing contact method (#3609) --- test/integration/email-cm.spec.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/test/integration/email-cm.spec.ts b/test/integration/email-cm.spec.ts index 5d9a4025e6..9cf8cfbc92 100644 --- a/test/integration/email-cm.spec.ts +++ b/test/integration/email-cm.spec.ts @@ -6,7 +6,7 @@ const c = new Chance() test.describe.configure({ mode: 'parallel' }) test.use({ storageState: userSessionFile }) -// test create, verify, and delete of an EMAIL contact method +// test create, edit, verify, and delete of an EMAIL contact method test('EMAIL contact method', async ({ page, browser, isMobile }) => { const name = 'pw-email ' + c.name() const email = 'pw-email-' + c.email() @@ -61,6 +61,8 @@ test('EMAIL contact method', async ({ page, browser, isMobile }) => { await page.click('[role=dialog] button[type=submit]') await page.locator('[role=dialog]').isHidden() + // edit name and enable status updates + const updatedName = 'updated name' await page .locator('.MuiCard-root', { has: page.locator('div > div > h2', { hasText: 'Contact Methods' }), @@ -68,7 +70,31 @@ test('EMAIL contact method', async ({ page, browser, isMobile }) => { .locator('li', { hasText: email }) .locator('[aria-label="Other Actions"]') .click() + await page.getByRole('menuitem', { name: 'Edit' }).click() + await page.fill('input[name=name]', updatedName) + await page.click('input[name=enableStatusUpdates]') + await page.click('[role=dialog] button[type=submit]') + // open edit dialog to verify name change and status updates are enabled + await page + .locator('.MuiCard-root', { + has: page.locator('div > div > h2', { hasText: 'Contact Methods' }), + }) + .locator('li', { hasText: email }) + .locator('[aria-label="Other Actions"]') + .click() + await page.getByRole('menuitem', { name: 'Edit' }).click() + await expect(page.locator('input[name=name]')).toHaveValue(updatedName) + await expect(page.locator('input[name=enableStatusUpdates]')).toBeChecked() + await page.click('[role=dialog] button[type=submit]') + + await page + .locator('.MuiCard-root', { + has: page.locator('div > div > h2', { hasText: 'Contact Methods' }), + }) + .locator('li', { hasText: email }) + .locator('[aria-label="Other Actions"]') + .click() await page.getByRole('menuitem', { name: 'Delete' }).click() await page.getByRole('button', { name: 'Confirm' }).click() await page From ff327ce7a2b5ef23b73af4c94979c8e46605de16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:48:16 -0600 Subject: [PATCH 05/18] build(deps): bump github.com/brianvoe/gofakeit/v6 from 6.26.4 to 6.28.0 (#3620) Bumps [github.com/brianvoe/gofakeit/v6](https://github.com/brianvoe/gofakeit) from 6.26.4 to 6.28.0. - [Release notes](https://github.com/brianvoe/gofakeit/releases) - [Commits](https://github.com/brianvoe/gofakeit/compare/v6.26.4...v6.28.0) --- updated-dependencies: - dependency-name: github.com/brianvoe/gofakeit/v6 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7c781448d1..96d59f7300 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.21 require ( github.com/99designs/gqlgen v0.17.42 - github.com/brianvoe/gofakeit/v6 v6.26.4 + github.com/brianvoe/gofakeit/v6 v6.28.0 github.com/coreos/go-oidc/v3 v3.9.0 github.com/creack/pty/v2 v2.0.1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc diff --git a/go.sum b/go.sum index 431dae711a..fade4e9ad5 100644 --- a/go.sum +++ b/go.sum @@ -650,8 +650,8 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4Yn github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/brianvoe/gofakeit/v6 v6.26.4 h1:+7JwTAXxw46Hdo1hA/F92Wi7x8vTwbjdFtBWYdm8eII= -github.com/brianvoe/gofakeit/v6 v6.26.4/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= +github.com/brianvoe/gofakeit/v6 v6.28.0 h1:Xib46XXuQfmlLS2EXRuJpqcw8St6qSZz75OUo0tgAW4= +github.com/brianvoe/gofakeit/v6 v6.28.0/go.mod h1:Xj58BMSnFqcn/fAQeSK+/PLtC5kSb7FJIq4JyGa8vEs= github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY= github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= From 5081c7d90310741819deebd52f0a940e976b1063 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:48:45 -0600 Subject: [PATCH 06/18] build(deps): bump github.com/vektah/gqlparser/v2 from 2.5.10 to 2.5.11 (#3618) Bumps [github.com/vektah/gqlparser/v2](https://github.com/vektah/gqlparser) from 2.5.10 to 2.5.11. - [Release notes](https://github.com/vektah/gqlparser/releases) - [Commits](https://github.com/vektah/gqlparser/compare/v2.5.10...v2.5.11) --- updated-dependencies: - dependency-name: github.com/vektah/gqlparser/v2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 96d59f7300..3c6677044f 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/spf13/viper v1.18.2 github.com/sqlc-dev/pqtype v0.3.0 github.com/stretchr/testify v1.8.4 - github.com/vektah/gqlparser/v2 v2.5.10 + github.com/vektah/gqlparser/v2 v2.5.11 golang.org/x/crypto v0.18.0 golang.org/x/oauth2 v0.16.0 golang.org/x/sys v0.16.0 diff --git a/go.sum b/go.sum index fade4e9ad5..83559e8304 100644 --- a/go.sum +++ b/go.sum @@ -1211,8 +1211,8 @@ github.com/vanng822/go-premailer v0.0.0-20191214114701-be27abe028fe/go.mod h1:JT github.com/vanng822/go-premailer v1.20.2 h1:vKs4VdtfXDqL7IXC2pkiBObc1bXM9bYH3Wa+wYw2DnI= github.com/vanng822/go-premailer v1.20.2/go.mod h1:RAxbRFp6M/B171gsKu8dsyq+Y5NGsUUvYfg+WQWusbE= github.com/vanng822/r2router v0.0.0-20150523112421-1023140a4f30/go.mod h1:1BVq8p2jVr55Ost2PkZWDrG86PiJ/0lxqcXoAcGxvWU= -github.com/vektah/gqlparser/v2 v2.5.10 h1:6zSM4azXC9u4Nxy5YmdmGu4uKamfwsdKTwp5zsEealU= -github.com/vektah/gqlparser/v2 v2.5.10/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= +github.com/vektah/gqlparser/v2 v2.5.11 h1:JJxLtXIoN7+3x6MBdtIP59TP1RANnY7pXOaDnADQSf8= +github.com/vektah/gqlparser/v2 v2.5.11/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= From 777fe6ee52489059fb1fcbf55ce641e2c7d2f6e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:49:23 -0600 Subject: [PATCH 07/18] build(deps-dev): bump @mui/system from 5.14.10 to 5.15.5 (#3613) Bumps [@mui/system](https://github.com/mui/material-ui/tree/HEAD/packages/mui-system) from 5.14.10 to 5.15.5. - [Release notes](https://github.com/mui/material-ui/releases) - [Changelog](https://github.com/mui/material-ui/blob/master/CHANGELOG.md) - [Commits](https://github.com/mui/material-ui/commits/v5.15.5/packages/mui-system) --- updated-dependencies: - dependency-name: "@mui/system" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 110 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 5b6eee5e30..2b0eecef1d 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@mui/lab": "5.0.0-alpha.145", "@mui/material": "5.14.10", "@mui/styles": "5.14.16", - "@mui/system": "5.14.10", + "@mui/system": "5.15.5", "@mui/x-data-grid": "6.18.7", "@playwright/test": "1.40.1", "@storybook/addon-essentials": "7.6.7", diff --git a/yarn.lock b/yarn.lock index 61bdbbae40..7f2eb5d01a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1531,6 +1531,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.23.8": + version: 7.23.8 + resolution: "@babel/runtime@npm:7.23.8" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 0bd5543c26811153822a9f382fd39886f66825ff2a397a19008011376533747cd05c33a91f6248c0b8b0edf0448d7c167ebfba34786088f1b7eb11c65be7dfc3 + languageName: node + linkType: hard + "@babel/template@npm:^7.22.15, @babel/template@npm:^7.22.5, @babel/template@npm:^7.3.3": version: 7.22.15 resolution: "@babel/template@npm:7.22.15" @@ -3056,7 +3065,7 @@ __metadata: languageName: node linkType: hard -"@mui/private-theming@npm:^5.14.10, @mui/private-theming@npm:^5.14.16, @mui/private-theming@npm:^5.15.2": +"@mui/private-theming@npm:^5.14.16, @mui/private-theming@npm:^5.15.2": version: 5.15.2 resolution: "@mui/private-theming@npm:5.15.2" dependencies: @@ -3073,7 +3082,24 @@ __metadata: languageName: node linkType: hard -"@mui/styled-engine@npm:^5.14.10, @mui/styled-engine@npm:^5.15.2": +"@mui/private-theming@npm:^5.15.5": + version: 5.15.5 + resolution: "@mui/private-theming@npm:5.15.5" + dependencies: + "@babel/runtime": ^7.23.8 + "@mui/utils": ^5.15.5 + prop-types: ^15.8.1 + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 3a5f7f190aa69a0ad69a34f77e54ee2fdcb088d21a61d4720b76d098d178e1c3f6a470fc5e7a30d351db10efbc062cb11af75b8789e2f38eaa2ca612bcb0f851 + languageName: node + linkType: hard + +"@mui/styled-engine@npm:^5.15.2": version: 5.15.2 resolution: "@mui/styled-engine@npm:5.15.2" dependencies: @@ -3094,6 +3120,27 @@ __metadata: languageName: node linkType: hard +"@mui/styled-engine@npm:^5.15.5": + version: 5.15.5 + resolution: "@mui/styled-engine@npm:5.15.5" + dependencies: + "@babel/runtime": ^7.23.8 + "@emotion/cache": ^11.11.0 + csstype: ^3.1.2 + prop-types: ^15.8.1 + peerDependencies: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 42bb7f50ed33ec88f799bd90a00337689837ee0b2d603681fe69c9a14850e5ae1d037505365ce78cd564307cc2c0654c46cece8d9250adb21c77b1de316e3c45 + languageName: node + linkType: hard + "@mui/styles@npm:5.14.16": version: 5.14.16 resolution: "@mui/styles@npm:5.14.16" @@ -3125,16 +3172,16 @@ __metadata: languageName: node linkType: hard -"@mui/system@npm:5.14.10": - version: 5.14.10 - resolution: "@mui/system@npm:5.14.10" +"@mui/system@npm:5.15.5": + version: 5.15.5 + resolution: "@mui/system@npm:5.15.5" dependencies: - "@babel/runtime": ^7.22.15 - "@mui/private-theming": ^5.14.10 - "@mui/styled-engine": ^5.14.10 - "@mui/types": ^7.2.4 - "@mui/utils": ^5.14.10 - clsx: ^2.0.0 + "@babel/runtime": ^7.23.8 + "@mui/private-theming": ^5.15.5 + "@mui/styled-engine": ^5.15.5 + "@mui/types": ^7.2.13 + "@mui/utils": ^5.15.5 + clsx: ^2.1.0 csstype: ^3.1.2 prop-types: ^15.8.1 peerDependencies: @@ -3149,7 +3196,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 16046084c125b52bcff38d4775bb2ed83ff39b46ee3b3c4b7dc9437f549ed2971343ee2035132f9bff7f5a198849622af2404ca674b2f3c142b6d343ab7f2d0e + checksum: 3f736f120d65fa14588cd7554a9ef63c9d4480716807d7ee5543b9f25d936b5ac0396a51a565c53b2905114fed5f94880c896f091e8d269260bfc53e8a8e2fb5 languageName: node linkType: hard @@ -3193,6 +3240,18 @@ __metadata: languageName: node linkType: hard +"@mui/types@npm:^7.2.13": + version: 7.2.13 + resolution: "@mui/types@npm:7.2.13" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 58dfc96f9654288519ff01d6b54e6a242f05cadad51210deb85710a81be4fa1501a116c8968e2614b16c748fc1f407dc23beeeeae70fa37fceb6c6de876ff70d + languageName: node + linkType: hard + "@mui/utils@npm:^5.14.10, @mui/utils@npm:^5.14.16, @mui/utils@npm:^5.14.3, @mui/utils@npm:^5.15.2": version: 5.15.2 resolution: "@mui/utils@npm:5.15.2" @@ -3211,6 +3270,24 @@ __metadata: languageName: node linkType: hard +"@mui/utils@npm:^5.15.5": + version: 5.15.5 + resolution: "@mui/utils@npm:5.15.5" + dependencies: + "@babel/runtime": ^7.23.8 + "@types/prop-types": ^15.7.11 + prop-types: ^15.8.1 + react-is: ^18.2.0 + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: fe71ce69e23cb4c00886138da1422e4baa66959cd10f97b2aa00f7324a038b13084ea4a0da123774c0018f48ddb6ce4f3cb20a915ec3c5c1ff5495bf337f665f + languageName: node + linkType: hard + "@mui/x-data-grid@npm:6.18.7": version: 6.18.7 resolution: "@mui/x-data-grid@npm:6.18.7" @@ -7970,6 +8047,13 @@ __metadata: languageName: node linkType: hard +"clsx@npm:^2.1.0": + version: 2.1.0 + resolution: "clsx@npm:2.1.0" + checksum: 43fefc29b6b49c9476fbce4f8b1cc75c27b67747738e598e6651dd40d63692135dc60b18fa1c5b78a2a9ba8ae6fd2055a068924b94e20b42039bd53b78b98e1d + languageName: node + linkType: hard + "co@npm:^4.6.0": version: 4.6.0 resolution: "co@npm:4.6.0" @@ -17476,7 +17560,7 @@ __metadata: "@mui/lab": 5.0.0-alpha.145 "@mui/material": 5.14.10 "@mui/styles": 5.14.16 - "@mui/system": 5.14.10 + "@mui/system": 5.15.5 "@mui/x-data-grid": 6.18.7 "@playwright/test": 1.40.1 "@storybook/addon-essentials": 7.6.7 From 6f98e1cb295bb4052bc0af9234e9fde577d89849 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:49:42 -0600 Subject: [PATCH 08/18] build(deps-dev): bump vite from 5.0.10 to 5.0.12 (#3612) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.10 to 5.0.12. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 2b0eecef1d..3d03fe7567 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,7 @@ "stylelint-config-standard": "34.0.0", "typescript": "5.2.2", "urql": "4.0.6", - "vite": "5.0.10", + "vite": "5.0.12", "wonka": "6.3.4", "wouter": "2.12.1" }, diff --git a/yarn.lock b/yarn.lock index 7f2eb5d01a..b9c5d6d9e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17655,7 +17655,7 @@ __metadata: stylelint-config-standard: 34.0.0 typescript: 5.2.2 urql: 4.0.6 - vite: 5.0.10 + vite: 5.0.12 wonka: 6.3.4 wouter: 2.12.1 dependenciesMeta: @@ -19835,9 +19835,9 @@ __metadata: languageName: node linkType: hard -"vite@npm:5.0.10": - version: 5.0.10 - resolution: "vite@npm:5.0.10" +"vite@npm:5.0.12": + version: 5.0.12 + resolution: "vite@npm:5.0.12" dependencies: esbuild: ^0.19.3 fsevents: ~2.3.3 @@ -19871,7 +19871,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: a1c96be1dc8bafb981c0874813a6b783ee9c4cd235188d7dc746133972d8992fe85111b7402365fee698ffcb626cd31b39bf2f2523140e50b07b81ce3c0139d1 + checksum: b97b6f1c204d9091d0973626827a6e9d8e8b1959ebd0877b6f76e7068e1e7adf9ecd3b1cc382cbab9d421e3eeca5e1a95f27f9c1734439b229f5a58ef2052fa4 languageName: node linkType: hard From adb75acdf8b0c923d5555ff0b921a6550139e6c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 12:45:27 -0600 Subject: [PATCH 09/18] build(deps): bump github.com/rubenv/sql-migrate from 1.6.0 to 1.6.1 (#3617) Bumps [github.com/rubenv/sql-migrate](https://github.com/rubenv/sql-migrate) from 1.6.0 to 1.6.1. - [Commits](https://github.com/rubenv/sql-migrate/compare/v1.6.0...v1.6.1) --- updated-dependencies: - dependency-name: github.com/rubenv/sql-migrate dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3c6677044f..e3c2a2693a 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/pelletier/go-toml/v2 v2.1.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.18.0 - github.com/rubenv/sql-migrate v1.6.0 + github.com/rubenv/sql-migrate v1.6.1 github.com/sirupsen/logrus v1.9.3 github.com/slack-go/slack v0.12.3 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index 83559e8304..3e8d2fa728 100644 --- a/go.sum +++ b/go.sum @@ -1128,8 +1128,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/rubenv/sql-migrate v1.6.0 h1:IZpcTlAx/VKXphWEpwWJ7BaMq05tYtE80zYz+8a5Il8= -github.com/rubenv/sql-migrate v1.6.0/go.mod h1:m3ilnKP7sNb4eYkLsp6cGdPOl4OBcXM6rcbzU+Oqc5k= +github.com/rubenv/sql-migrate v1.6.1 h1:bo6/sjsan9HaXAsNxYP/jCEDUGibHp8JmOBw7NTGRos= +github.com/rubenv/sql-migrate v1.6.1/go.mod h1:tPzespupJS0jacLfhbwto/UjSX+8h2FdWB7ar+QlHa0= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= From aa1897107ba6be8ac1d9ff42f248ea06471b7500 Mon Sep 17 00:00:00 2001 From: Katie Sydlik-Badgerow <38022260+KatieMSB@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:45:54 -0500 Subject: [PATCH 10/18] ui/temp-sched: Add fixed shifts for temporary schedules (#3606) * add fixed shifts to temporary schedule * use shift duration in list for coverage gap calc * fix inferred duration * add custom shift option * add day abreviation for 24hr shifts * run make check and update unit tests * fix coverage gap test * update cypress tests * remove add shift accordion * clean up inferred duration logic * formatting * make duration nullable and fix interval label * formatting * formatting * update cypress tests * use shift duration on shifts --- .../temp-sched/TempSchedAddNewShift.tsx | 285 +++++++++--------- .../schedules/temp-sched/TempSchedDialog.tsx | 92 +++++- .../temp-sched/TempSchedShiftsList.tsx | 37 ++- .../app/schedules/temp-sched/sharedUtils.tsx | 25 +- .../temp-sched/shiftsListUtil.test.ts | 37 ++- .../schedules/temp-sched/shiftsListUtil.tsx | 43 +-- web/src/app/util/luxon-helpers.test.ts | 16 +- web/src/app/util/luxon-helpers.ts | 23 +- web/src/app/util/timeFormat.ts | 17 +- web/src/cypress/e2e/temporarySchedule.cy.ts | 43 +-- 10 files changed, 387 insertions(+), 231 deletions(-) diff --git a/web/src/app/schedules/temp-sched/TempSchedAddNewShift.tsx b/web/src/app/schedules/temp-sched/TempSchedAddNewShift.tsx index d9444a91a3..69d5c48c53 100644 --- a/web/src/app/schedules/temp-sched/TempSchedAddNewShift.tsx +++ b/web/src/app/schedules/temp-sched/TempSchedAddNewShift.tsx @@ -1,16 +1,11 @@ import React, { useEffect, useState } from 'react' -import { Button, Grid } from '@mui/material' -import Accordion from '@mui/material/Accordion' -import AccordionActions from '@mui/material/AccordionActions' -import AccordionSummary from '@mui/material/AccordionSummary' -import AccordionDetails from '@mui/material/AccordionDetails' +import { Button, Checkbox, FormControlLabel, Grid } from '@mui/material' import Typography from '@mui/material/Typography' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import ToggleIcon from '@mui/icons-material/CompareArrows' import _ from 'lodash' import { dtToDuration, Shift, TempSchedValue } from './sharedUtils' import { FormContainer, FormField } from '../../forms' -import { DateTime, Interval } from 'luxon' +import { DateTime, Duration, Interval } from 'luxon' import { FieldError } from '../../util/errutil' import { isISOAfter } from '../../util/shifts' import { useScheduleTZ } from '../useScheduleTZ' @@ -79,14 +74,13 @@ export default function TempSchedAddNewShift({ scheduleID, onChange, value, - showForm, - setShowForm, shift, setShift, }: AddShiftsStepProps): JSX.Element { const [submitted, setSubmitted] = useState(false) - const [manualEntry, setManualEntry] = useState(false) + const [custom, setCustom] = useState(false) + const [manualEntry, setManualEntry] = useState(true) const { q, zone, isLocalZone } = useScheduleTZ(scheduleID) // set start equal to the temporary schedule's start @@ -96,11 +90,13 @@ export default function TempSchedAddNewShift({ setShift({ start: value.start, - end: DateTime.fromISO(value.start, { zone }).plus({ hours: 8 }).toISO(), + end: DateTime.fromISO(value.start, { zone }) + .plus(value.shiftDur as Duration) + .toISO(), userID: '', truncated: false, }) - }, [value.start, zone]) + }, [value.start, zone, value.shiftDur]) // fieldErrors handles errors manually through the client // as this step form is nested inside the greater form @@ -136,13 +132,13 @@ export default function TempSchedAddNewShift({ onChange(mergeShifts(value.shifts.concat(shift))) const end = DateTime.fromISO(shift.end, { zone }) - const diff = end.diff(DateTime.fromISO(shift.start, { zone })) setShift({ userID: '', truncated: false, start: shift.end, - end: end.plus(diff).toISO(), + end: end.plus(value.shiftDur as Duration).toISO(), }) + setCustom(false) setSubmitted(false) } @@ -152,137 +148,132 @@ export default function TempSchedAddNewShift({ value={shift} onChange={(val: Shift) => setShift(val)} > - setShowForm(!showForm)} - expanded={showForm} - > - } - data-cy='add-shift-expander' - > - - ADD SHIFT - - - - - - - - - { - if (!manualEntry) { - const diff = DateTime.fromISO(value, { zone }).diff( - DateTime.fromISO(formValue.start, { zone }), - ) - formValue.end = DateTime.fromISO(formValue.end, { zone }) - .plus(diff) - .toISO() - } - return value - }} - timeZone={zone} - disabled={q.loading} - hint={isLocalZone ? '' : fmtLocal(value?.start)} - /> - - - {manualEntry ? ( - - {!isLocalZone && fmtLocal(value?.end)} -
- setManualEntry(false)} - endIcon={} - > - Configure as duration - -
- - } - timeZone={zone} - disabled={q.loading} - /> - ) : ( - { - const nextValDT = DateTime.fromISO(nextVal, { zone }) - const formValDT = DateTime.fromISO(formValue?.start ?? '', { - zone, - }) - const duration = dtToDuration(formValDT, nextValDT) - return duration === -1 ? '' : duration.toString() - }} - // value held in state - mapOnChangeValue={( - nextVal: string, - formValue: TempSchedValue, - ) => { - if (!nextVal) return '' - return DateTime.fromISO(formValue.start, { zone }) - .plus({ hours: parseFloat(nextVal) }) - .toISO() - }} - step='any' - min={0} - disabled={q.loading} - hint={ - setManualEntry(true)} - endIcon={} - > - Configure as date/time - - } - /> - )} -
-
-
- + + + Add Shift + + + + + + } + label={ + + Configure custom shift + + } + onChange={() => setCustom(!custom)} + /> + + + { + if (!manualEntry) { + const diff = DateTime.fromISO(value, { zone }).diff( + DateTime.fromISO(formValue.start, { zone }), + ) + formValue.end = DateTime.fromISO(formValue.end, { zone }) + .plus(diff) + .toISO() + } + return value + }} + timeZone={zone} + disabled={q.loading || !custom} + hint={isLocalZone ? '' : fmtLocal(value?.start)} + /> + + + {manualEntry ? ( + + {!isLocalZone && fmtLocal(value?.end)} +
+ setManualEntry(false)} + endIcon={} + > + Configure as duration + +
+ + ) : null + } + timeZone={zone} + disabled={q.loading || !custom} + /> + ) : ( + { + const nextValDT = DateTime.fromISO(nextVal, { zone }) + const formValDT = DateTime.fromISO(formValue?.start ?? '', { + zone, + }) + const duration = dtToDuration(formValDT, nextValDT) + return duration === -1 ? '' : duration.toString() + }} + // value held in state + mapOnChangeValue={( + nextVal: string, + formValue: TempSchedValue, + ) => { + if (!nextVal) return '' + return DateTime.fromISO(formValue.start, { zone }) + .plus({ hours: parseFloat(nextVal) }) + .toISO() + }} + step='any' + min={0} + disabled={q.loading || !custom} + hint={ + custom ? ( + setManualEntry(true)} + endIcon={} + > + Configure as date/time + + ) : null + } + /> + )} +
+ -
-
+ + ) } diff --git a/web/src/app/schedules/temp-sched/TempSchedDialog.tsx b/web/src/app/schedules/temp-sched/TempSchedDialog.tsx index 9fc6a0ce17..6d54e49994 100644 --- a/web/src/app/schedules/temp-sched/TempSchedDialog.tsx +++ b/web/src/app/schedules/temp-sched/TempSchedDialog.tsx @@ -11,10 +11,15 @@ import { Theme } from '@mui/material/styles' import Alert from '@mui/material/Alert' import AlertTitle from '@mui/material/AlertTitle' import _ from 'lodash' -import { DateTime, Interval } from 'luxon' +import { DateTime, Duration, Interval } from 'luxon' import { fieldErrors, nonFieldErrors } from '../../util/errutil' import FormDialog from '../../dialogs/FormDialog' -import { contentText, dtToDuration, Shift, TempSchedValue } from './sharedUtils' +import { + contentText, + inferDuration, + Shift, + TempSchedValue, +} from './sharedUtils' import { FormContainer, FormField } from '../../forms' import TempSchedAddNewShift from './TempSchedAddNewShift' import { isISOAfter, parseInterval } from '../../util/shifts' @@ -25,6 +30,7 @@ import { getCoverageGapItems } from './shiftsListUtil' import { fmtLocal } from '../../util/timeFormat' import { ensureInterval } from '../timeUtil' import TempSchedConfirmation from './TempSchedConfirmation' +import { TextField, MenuItem, Divider } from '@mui/material' const mutation = gql` mutation ($input: SetTemporaryScheduleInput!) { @@ -84,6 +90,11 @@ const clampForward = (nowISO: string, iso: string): string => { return iso } +interface DurationValues { + dur: number + ivl: string +} + export default function TempSchedDialog({ onClose, scheduleID, @@ -94,6 +105,25 @@ export default function TempSchedDialog({ const { q, zone, isLocalZone } = useScheduleTZ(scheduleID) const [now] = useState(DateTime.utc().startOf('minute').toISO()) const [showForm, setShowForm] = useState(false) + + let defaultShiftDur = {} as DurationValues + + const getDurValues = (dur: Duration): DurationValues => { + if (dur.hours < 24 && dur.days < 1) + return { ivl: 'hours', dur: Math.ceil(dur.hours) } + if (dur.days < 7) return { ivl: 'days', dur: Math.ceil(dur.days) } + return { ivl: 'weeks', dur: Math.ceil(dur.weeks) } + } + + if (edit) { + // if editing infer shift duration + defaultShiftDur = getDurValues(inferDuration(_value.shifts)) + } else { + defaultShiftDur = getDurValues(_value?.shiftDur as Duration) + } + + const [durValues, setDurValues] = useState(defaultShiftDur) + const [value, setValue] = useState({ start: clampForward(now, _value.start), end: _value.end, @@ -110,11 +140,14 @@ export default function TempSchedDialog({ } return true }), + shiftDur: + _value.shiftDur || + Duration.fromObject({ [durValues.ivl]: durValues.dur }), }) const startDT = DateTime.fromISO(value.start, { zone }) const [shift, setShift] = useState({ start: startDT.toISO(), - end: startDT.plus({ hours: 8 }).toISO(), + end: startDT.plus(value.shiftDur).toISO(), userID: '', truncated: false, }) @@ -153,12 +186,8 @@ export default function TempSchedDialog({ function handleCoverageGapClick(coverageGap: Interval): void { if (!showForm) setShowForm(true) - // make sure duration remains the same (evaluated off of the end timestamp) - const startDT = DateTime.fromISO(shift?.start ?? '', { zone }) - const endDT = DateTime.fromISO(shift?.end ?? '', { zone }) - const duration = dtToDuration(startDT, endDT) const nextStart = coverageGap?.start - const nextEnd = nextStart.plus({ hours: duration }) + const nextEnd = nextStart.plus(value.shiftDur) setShift({ userID: shift?.userID ?? '', @@ -174,6 +203,7 @@ export default function TempSchedDialog({ return ( getCoverageGapItems( schedInterval, + value.shiftDur, value.shifts, zone, handleCoverageGapClick, @@ -351,6 +381,51 @@ export default function TempSchedDialog({ /> + { + setDurValues({ ...durValues, ...newValue }) + setValue({ + ...value, + shiftDur: Duration.fromObject({ + [newValue.ivl]: newValue.dur, + }), + }) + }} + > + + validate()} + disabled={q.loading} + /> + + + validate()} + disabled={q.loading} + > + Hour + Day + Week + + + + + + + + { diff --git a/web/src/app/schedules/temp-sched/TempSchedShiftsList.tsx b/web/src/app/schedules/temp-sched/TempSchedShiftsList.tsx index 53b2cee475..433f897f5a 100644 --- a/web/src/app/schedules/temp-sched/TempSchedShiftsList.tsx +++ b/web/src/app/schedules/temp-sched/TempSchedShiftsList.tsx @@ -20,7 +20,7 @@ import { UserAvatar } from '../../util/avatars' import { useUserInfo } from '../../util/useUserInfo' import { parseInterval } from '../../util/shifts' import { useScheduleTZ } from '../useScheduleTZ' -import { splitAtMidnight } from '../../util/luxon-helpers' +import { splitShift } from '../../util/luxon-helpers' import { getCoverageGapItems, getSubheaderItems, @@ -46,6 +46,7 @@ const useStyles = makeStyles({ type TempSchedShiftsListProps = { value: Shift[] onRemove?: (shift: Shift) => void + shiftDur?: Duration start: string end: string edit?: boolean @@ -62,6 +63,7 @@ export default function TempSchedShiftsList({ edit, start, end, + shiftDur, value, compareAdditions, compareRemovals, @@ -115,28 +117,40 @@ export default function TempSchedShiftsList({ ] } - const subheaderItems = getSubheaderItems(schedInterval, shifts, zone) const coverageGapItems = getCoverageGapItems( schedInterval, + shiftDur as Duration, shifts, zone, handleCoverageGapClick, ) + const subheaderItems = getSubheaderItems( + schedInterval, + shifts, + shiftDur as Duration, + zone, + ) + const outOfBoundsItems = getOutOfBoundsItems(schedInterval, shifts, zone) const shiftItems = (() => { return _.flatMap(shifts, (s: Shift, idx) => { const shiftInv = parseInterval(s, zone) const isValid = schedInterval.engulfs(shiftInv) - const dayInvs = splitAtMidnight(shiftInv) + let fixedShifts = splitShift(shiftInv) + + // splitShift with shift duration if duration spans multiple days, otherwise default to 1 day + if (shiftDur?.days && shiftDur?.days > 1) + fixedShifts = splitShift(shiftInv, shiftDur) - return dayInvs.map((inv, index) => { + return fixedShifts.map((inv, index) => { const startTime = fmtTime( s.displayStart ? s.displayStart : inv.start, zone, false, + false, ) - const endTime = fmtTime(inv.end, zone, false) + const endTime = fmtTime(inv.end, zone, false, false) const shiftExists = existingShifts.find((shift) => { return ( DateTime.fromISO(s.start).equals(DateTime.fromISO(shift.start)) && @@ -152,10 +166,15 @@ export default function TempSchedShiftsList({ let titleText = '' if (inv.length('hours') === 24) { // shift spans all day - subText = 'All day' + subText = `All day` } else if (inv.engulfs(shiftInv)) { - // shift is inside the day + // shift is inside the interval subText = `From ${startTime} to ${endTime}` + if (inv.length('days') > 1) { + subText = `From ${inv.start.toFormat( + 't ccc', + )} to ${inv.end.toFormat('t ccc')}` + } titleText = `From ${fmtLocal(inv.start.toISO())} to ${fmtLocal( inv.end.toISO(), )}` @@ -263,7 +282,7 @@ export default function TempSchedShiftsList({ } : { message: '', - details: `Starts at ${fmtTime(start, zone, false)}`, + details: `Starts at ${fmtTime(start, zone, false, false)}`, at: DateTime.fromISO(start, { zone }), itemType: 'start', tooltipTitle: `Starts at ${fmtLocal(start)}`, @@ -288,7 +307,7 @@ export default function TempSchedShiftsList({ const at = DateTime.fromISO(end, { zone }) const details = at.equals(at.startOf('day')) ? 'Ends at midnight' - : 'Ends at ' + fmtTime(at, zone, false) + : 'Ends at ' + fmtTime(at, zone, false, false) const detailsTooltip = `Ends at ${fmtLocal(end)}` return { diff --git a/web/src/app/schedules/temp-sched/sharedUtils.tsx b/web/src/app/schedules/temp-sched/sharedUtils.tsx index 38dd22a0cb..5ea6edeb01 100644 --- a/web/src/app/schedules/temp-sched/sharedUtils.tsx +++ b/web/src/app/schedules/temp-sched/sharedUtils.tsx @@ -1,10 +1,11 @@ -import { DateTime } from 'luxon' +import { DateTime, Duration, Interval } from 'luxon' import React, { ReactNode } from 'react' export type TempSchedValue = { start: string end: string shifts: Shift[] + shiftDur?: Duration } export type Shift = { @@ -13,6 +14,7 @@ export type Shift = { end: string userID: string truncated: boolean + custom?: boolean user?: null | { id: string @@ -20,6 +22,26 @@ export type Shift = { } } +export function inferDuration(shifts: Shift[]): Duration { + const totalDurations = shifts.reduce((acc, shift) => { + const startDateTime = DateTime.fromISO(shift.start) + const endDateTime = DateTime.fromISO(shift.end) + + if (startDateTime.isValid && endDateTime.isValid) { + const interval = Interval.fromDateTimes(startDateTime, endDateTime) + return acc.plus(interval.toDuration()) + } + + return acc + }, Duration.fromObject({})) + + return Duration.fromObject({ + hours: totalDurations.as('hours') / shifts.length, + days: totalDurations.as('days') / shifts.length, + weeks: totalDurations.as('weeks') / shifts.length, + }) +} + // defaultTempScheduleValue returns a timespan, with no shifts, // of the following week. export function defaultTempSchedValue(zone: string): TempSchedValue { @@ -35,6 +57,7 @@ export function defaultTempSchedValue(zone: string): TempSchedValue { start: startDT.toISO(), end: startDT.plus({ days: 7 }).toISO(), shifts: [], + shiftDur: Duration.fromObject({ days: 1 }), } } diff --git a/web/src/app/schedules/temp-sched/shiftsListUtil.test.ts b/web/src/app/schedules/temp-sched/shiftsListUtil.test.ts index 31644048ac..bcdeba22aa 100644 --- a/web/src/app/schedules/temp-sched/shiftsListUtil.test.ts +++ b/web/src/app/schedules/temp-sched/shiftsListUtil.test.ts @@ -1,4 +1,4 @@ -import { DateTime, Interval } from 'luxon' +import { DateTime, Duration, Interval } from 'luxon' import { Shift } from './sharedUtils' import { getCoverageGapItems, @@ -15,6 +15,7 @@ const newYork = 'America/New_York' interface TestConfig { name: string schedIntervalISO: string + shiftDuration?: Duration shifts: Shift[] // expected is an array of start times for each coverage gap expected: string[] @@ -27,7 +28,12 @@ describe('getSubheaderItems', () => { const schedInterval = Interval.fromISO(tc.schedIntervalISO, { zone: tc.zone, }) - const result = getSubheaderItems(schedInterval, tc.shifts, tc.zone) + const result = getSubheaderItems( + schedInterval, + tc.shifts, + tc.shiftDuration as Duration, + tc.zone, + ) expect(result).toHaveLength(tc.expected.length) expect(_.uniq(result.map((r) => r.id))).toHaveLength(tc.expected.length) @@ -44,6 +50,7 @@ describe('getSubheaderItems', () => { name: '0 hr sched interval; no shifts', schedIntervalISO: `${'2021-08-13T00:00:00.000-05:00'}/${'2021-08-13T00:00:00.000-05:00'}`, shifts: [], + shiftDuration: Duration.fromObject({ hours: 0 }), expected: [], zone: chicago, }) @@ -52,6 +59,7 @@ describe('getSubheaderItems', () => { name: '1 hr sched interval; no shifts', schedIntervalISO: `${'2021-08-13T00:00:00.000-05:00'}/${'2021-08-13T01:00:00.000-05:00'}`, shifts: [], + shiftDuration: Duration.fromObject({ hours: 1 }), expected: ['Friday, August 13'], zone: chicago, }) @@ -60,6 +68,7 @@ describe('getSubheaderItems', () => { name: '1 hr sched interval; no shifts; alternate zone', schedIntervalISO: `${'2021-08-13T00:00:00.000-05:00'}/${'2021-08-13T01:00:00.000-05:00'}`, shifts: [], + shiftDuration: Duration.fromObject({ hours: 1 }), expected: ['Friday, August 13'], zone: newYork, }) @@ -68,6 +77,7 @@ describe('getSubheaderItems', () => { name: '24 hr sched interval; no shifts', schedIntervalISO: `${'2021-08-13T00:00:00.000-05:00'}/${'2021-08-14T00:00:00.000-05:00'}`, shifts: [], + shiftDuration: Duration.fromObject({ hours: 24 }), expected: ['Friday, August 13'], zone: chicago, }) @@ -76,7 +86,8 @@ describe('getSubheaderItems', () => { name: '25 hr sched interval; no shifts', schedIntervalISO: `${'2021-08-13T00:00:00.000-05:00'}/${'2021-08-14T01:00:00.000-05:00'}`, shifts: [], - expected: ['Friday, August 13', 'Saturday, August 14'], + shiftDuration: Duration.fromObject({ hours: 25 }), + expected: ['Friday, August 13'], zone: chicago, }) @@ -84,7 +95,8 @@ describe('getSubheaderItems', () => { name: '50 hr sched interval; no shifts', schedIntervalISO: `${'2021-08-13T00:00:00.000-05:00'}/${'2021-08-15T02:00:00.000-05:00'}`, shifts: [], - expected: ['Friday, August 13', 'Saturday, August 14', 'Sunday, August 15'], + shiftDuration: Duration.fromObject({ hours: 50 }), + expected: ['Friday, August 13'], zone: chicago, }) @@ -99,6 +111,7 @@ describe('getSubheaderItems', () => { truncated: false, }, ], + shiftDuration: Duration.fromObject({ hours: 24 }), expected: ['Thursday, August 12', 'Friday, August 13'], zone: chicago, }) @@ -114,6 +127,7 @@ describe('getSubheaderItems', () => { truncated: false, }, ], + shiftDuration: Duration.fromObject({ hours: 24 }), expected: ['Friday, August 13'], zone: chicago, }) @@ -129,6 +143,7 @@ describe('getSubheaderItems', () => { truncated: false, }, ], + shiftDuration: Duration.fromObject({ hours: 24 }), expected: [ 'Friday, August 13', 'Saturday, August 14', @@ -161,7 +176,8 @@ describe('getSubheaderItems', () => { truncated: false, }, ], - expected: ['Friday, August 13', 'Saturday, August 14', 'Sunday, August 15'], + shiftDuration: Duration.fromObject({ hours: 30 }), + expected: ['Friday, August 13', 'Saturday, August 14'], zone: chicago, }) }) @@ -174,6 +190,7 @@ describe('getCoverageGapItems', () => { }) const result = getCoverageGapItems( schedInterval, + tc.shiftDuration as Duration, tc.shifts, tc.zone, () => {}, @@ -184,9 +201,8 @@ describe('getCoverageGapItems', () => { result.forEach((r, i) => { expect(r.at.zoneName).toEqual(tc.zone) - expect(r.at).toEqual( - DateTime.fromISO(tc.expected[i], { zone: tc.zone }), - ) + const expectedDT = DateTime.fromISO(tc.expected[i], { zone: tc.zone }) + expect(r.at.toISO()).toEqual(expectedDT.toISO()) }) }) } @@ -195,6 +211,7 @@ describe('getCoverageGapItems', () => { name: '0 hr sched interval; no shifts', schedIntervalISO: `${'2021-08-13T00:00:00.000-05:00'}/${'2021-08-13T00:00:00.000-05:00'}`, shifts: [], + shiftDuration: Duration.fromObject({ hours: 0 }), expected: [], zone: chicago, }) @@ -203,6 +220,7 @@ describe('getCoverageGapItems', () => { name: '1 hr sched interval; no shifts; alternate zone', schedIntervalISO: `${'2021-08-13T00:00:00.000-05:00'}/${'2021-08-13T01:00:00.000-05:00'}`, shifts: [], + shiftDuration: Duration.fromObject({ hours: 1 }), expected: ['2021-08-13T00:00:00.000-05:00'], zone: newYork, }) @@ -218,6 +236,7 @@ describe('getCoverageGapItems', () => { truncated: false, }, ], + shiftDuration: Duration.fromObject({ hours: 3 }), expected: [ '2021-08-13T00:00:00.000-05:00', '2021-08-13T02:00:00.000-05:00', @@ -236,6 +255,7 @@ describe('getCoverageGapItems', () => { truncated: false, }, ], + shiftDuration: Duration.fromObject({ hours: 3 }), expected: ['2021-08-13T00:00:00.000-05:00'], zone: chicago, }) @@ -251,6 +271,7 @@ describe('getCoverageGapItems', () => { truncated: false, }, ], + shiftDuration: Duration.fromObject({ hours: 3 }), expected: ['2021-08-13T01:00:00.000-05:00'], zone: chicago, }) diff --git a/web/src/app/schedules/temp-sched/shiftsListUtil.tsx b/web/src/app/schedules/temp-sched/shiftsListUtil.tsx index 4030c51bb8..f04365e257 100644 --- a/web/src/app/schedules/temp-sched/shiftsListUtil.tsx +++ b/web/src/app/schedules/temp-sched/shiftsListUtil.tsx @@ -1,13 +1,13 @@ import React from 'react' import _ from 'lodash' -import { DateTime, Interval } from 'luxon' +import { DateTime, Duration, Interval } from 'luxon' import { FlatListListItem, FlatListNotice, FlatListSub, } from '../../lists/FlatList' -import { ExplicitZone, splitAtMidnight } from '../../util/luxon-helpers' +import { ExplicitZone, splitShift } from '../../util/luxon-helpers' import { parseInterval } from '../../util/shifts' import { Shift } from './sharedUtils' import { Tooltip } from '@mui/material' @@ -31,6 +31,7 @@ export type Sortable = T & { export function getSubheaderItems( schedInterval: Interval, shifts: Shift[], + shiftDur: Duration, zone: ExplicitZone, ): Sortable[] { if (!schedInterval.isValid) { @@ -48,8 +49,9 @@ export function getSubheaderItems( ...shifts.map((s) => DateTime.fromISO(s.end, { zone })), ) - const dayInvs = splitAtMidnight( + const dayInvs = splitShift( Interval.fromDateTimes(lowerBound, upperBound), + shiftDur, ) return dayInvs.map((day) => { @@ -94,8 +96,8 @@ export function getOutOfBoundsItems( upperBound, ).mapEndpoints((e) => e.plus({ day: 1 }).startOf('day')) // ensure sched end date is not included - const daysBeforeStart = splitAtMidnight(beforeStart) - const daysAfterEnd = splitAtMidnight(afterEnd) + const daysBeforeStart = splitShift(beforeStart) + const daysAfterEnd = splitShift(afterEnd) const intervals = daysBeforeStart.concat(daysAfterEnd) let details = '' @@ -119,6 +121,7 @@ export function getOutOfBoundsItems( export function getCoverageGapItems( schedInterval: Interval, + shiftDuration: Duration, shifts: Shift[], zone: ExplicitZone, handleCoverageClick?: (coverageGap: Interval) => void, @@ -129,7 +132,7 @@ export function getCoverageGapItems( const shiftIntervals = shifts.map((s) => parseInterval(s, zone)) const gapIntervals = _.flatMap( schedInterval.difference(...shiftIntervals), - (inv) => splitAtMidnight(inv), + (inv) => splitShift(inv, shiftDuration), ) const isLocalZone = zone === DateTime.local().zoneName return gapIntervals.map((gap) => { @@ -138,19 +141,23 @@ export function getCoverageGapItems( if (gap.length('hours') === 24) { // nothing to do title = '' - } else if (gap.start.equals(gap.start.startOf('day'))) { - details += ` until ${fmtTime(gap.end, zone, false)}` - title += ` until ${fmtLocal(gap.end)}` - } else if (gap.end.equals(gap.start.plus({ day: 1 }).startOf('day'))) { - details += ` after ${fmtTime(gap.start, zone, false)}` - title += ` after ${fmtLocal(gap.start)}` } else { - details += ` from ${fmtTime(gap.start, zone, false)} to ${fmtTime( - gap.end, - zone, - false, - )}` - title += ` from ${fmtLocal(gap.start)} to ${fmtLocal(gap.end)}` + if (!gap.start.hasSame(gap.end, 'day')) { + details += ` from ${fmtTime(gap.start, zone, false, true)} to ${fmtTime( + gap.end, + zone, + false, + true, + )}` + } else { + details += ` from ${fmtTime( + gap.start, + zone, + false, + false, + )} to ${fmtTime(gap.end, zone, false, false)}` + } + title += `from ${fmtLocal(gap.start)} to ${fmtLocal(gap.end)}` } return { diff --git a/web/src/app/util/luxon-helpers.test.ts b/web/src/app/util/luxon-helpers.test.ts index d825eb9995..3463a0ee0b 100644 --- a/web/src/app/util/luxon-helpers.test.ts +++ b/web/src/app/util/luxon-helpers.test.ts @@ -3,7 +3,7 @@ import { Chance } from 'chance' import { getStartOfWeek, getEndOfWeek, - splitAtMidnight, + splitShift, getNextWeekday, } from './luxon-helpers' @@ -296,7 +296,7 @@ describe('getNextWeekday', () => { }) }) -describe('splitAtMidnight', () => { +describe('splitShift', () => { function rand(): DateTime { const c = new Chance() return DateTime.fromJSDate(c.date()) @@ -307,7 +307,7 @@ describe('splitAtMidnight', () => { DateTime.fromObject({ hour: 1 }), DateTime.fromObject({ hour: 2 }), ) - const result = splitAtMidnight(hour) + const result = splitShift(hour) expect(result.length).toEqual(1) expect(result[0]).toEqual(hour) }) @@ -317,7 +317,7 @@ describe('splitAtMidnight', () => { DateTime.fromObject({ hour: 0 }).startOf('day'), DateTime.fromObject({ hour: 2 }), ) - const result = splitAtMidnight(startOfDay) + const result = splitShift(startOfDay) expect(result.length).toEqual(1) expect(result[0]).toEqual(startOfDay) }) @@ -326,7 +326,7 @@ describe('splitAtMidnight', () => { const start = rand().startOf('day') const end = start.plus({ day: 1 }).startOf('day') const inv = Interval.fromDateTimes(start, end) - const result = splitAtMidnight(inv) + const result = splitShift(inv) expect(result.length).toEqual(1) expect(result[0]).toEqual(inv) @@ -336,7 +336,7 @@ describe('splitAtMidnight', () => { const start = rand().set({ hour: 4 }) const end = start.plus({ day: 1 }).startOf('day') const inv = Interval.fromDateTimes(start, end) - const result = splitAtMidnight(inv) + const result = splitShift(inv) expect(result.length).toEqual(1) expect(result[0]).toEqual(inv) @@ -346,7 +346,7 @@ describe('splitAtMidnight', () => { const start = rand().startOf('day') const end = start.plus({ day: 1, hour: 4 }) const inv = Interval.fromDateTimes(start, end) - const result = splitAtMidnight(inv) + const result = splitShift(inv) expect(result.length).toEqual(2) expect(result[0]).toEqual(Interval.fromDateTimes(start, end.startOf('day'))) @@ -357,7 +357,7 @@ describe('splitAtMidnight', () => { const start = rand().set({ hour: 4 }) const end = start.plus({ day: 3 }) const inv = Interval.fromDateTimes(start, end) - const result = splitAtMidnight(inv) + const result = splitShift(inv) expect(result.length).toEqual(4) expect(result[0]).toEqual( diff --git a/web/src/app/util/luxon-helpers.ts b/web/src/app/util/luxon-helpers.ts index c94b2bd3f6..a5fdf24149 100644 --- a/web/src/app/util/luxon-helpers.ts +++ b/web/src/app/util/luxon-helpers.ts @@ -1,4 +1,4 @@ -import { DateTime, DateTimeOptions, Interval } from 'luxon' +import { DateTime, DateTimeOptions, Duration, Interval } from 'luxon' export type ExplicitZone = NonNullable @@ -55,16 +55,25 @@ export function getNextWeekday( return start.plus({ days: weekday + (7 - start.weekday) }).startOf('day') } -export function splitAtMidnight(inv: Interval): Interval[] { +export function splitShift(inv: Interval, dur?: Duration): Interval[] { + let duration = Duration.fromObject({ days: 1 }) + if (dur && dur.isValid) { + duration = dur + } + // dummy interval shifted forward 1 day - const dummy = inv.mapEndpoints((e) => e.plus({ day: 1 })) + const dummy = inv.mapEndpoints((e) => e.plus(duration)) - const midnights: DateTime[] = [] + const shifts: DateTime[] = [] let iter = dummy.start while (iter < dummy.end) { - midnights.push(iter.startOf('day')) - iter = iter.plus({ day: 1 }) + if (dur && dur.isValid) { + shifts.push(iter) + } else { + shifts.push(iter.startOf('day')) + } + iter = iter.plus(duration) } - return inv.splitAt(...midnights) + return inv.splitAt(...shifts) } diff --git a/web/src/app/util/timeFormat.ts b/web/src/app/util/timeFormat.ts index 65b34e19f2..0d0b9ebdc9 100644 --- a/web/src/app/util/timeFormat.ts +++ b/web/src/app/util/timeFormat.ts @@ -198,6 +198,7 @@ export function fmtTime( time: DateTime | string, zone: ExplicitZone, withZoneAbbr: boolean | null = null, + withDay: boolean, ): string { if (!time) return '' if (typeof time === 'string') { @@ -207,13 +208,17 @@ export function fmtTime( } const prefix = time.toLocaleString(DateTime.TIME_SIMPLE) - const suffix = time.toFormat('ZZZZ') + const zoneSuffix = time.toFormat('ZZZZ') + const daySuffix = time.toFormat('ccc') - if (withZoneAbbr === true) return prefix + ' ' + suffix - if (withZoneAbbr === false) return prefix + let timeString = prefix - if (zone === DateTime.local().zoneName) return prefix - return prefix + ' ' + suffix + if (withDay) timeString = timeString + ' ' + daySuffix + if (withZoneAbbr === true) return timeString + ' ' + zoneSuffix + if (withZoneAbbr === false) return timeString + + if (zone === DateTime.local().zoneName) return timeString + return timeString + ' ' + zoneSuffix } // fmtLocal is like fmtTime but uses the system zone and displays zone info by default. @@ -221,5 +226,5 @@ export function fmtLocal( time: DateTime | string, withZoneAbbr: boolean | null = true, ): string { - return fmtTime(time, 'local', withZoneAbbr) + return fmtTime(time, 'local', withZoneAbbr, false) } diff --git a/web/src/cypress/e2e/temporarySchedule.cy.ts b/web/src/cypress/e2e/temporarySchedule.cy.ts index d0096c6284..834d5801e1 100644 --- a/web/src/cypress/e2e/temporarySchedule.cy.ts +++ b/web/src/cypress/e2e/temporarySchedule.cy.ts @@ -27,21 +27,16 @@ function testTemporarySchedule(screen: string): void { const schedTZ = (t: DateTime): DateTime => t.setZone(schedule.timeZone) - it('should toggle duration field', () => { - const defaultDurationHrs = 8 + it('should toggle duration field for custom shift', () => { + const defaultDurationHrs = 24 cy.get('[data-cy="new-override"]').click() cy.dialogTitle('Choose') cy.dialogForm({ variant: 'temp' }) cy.dialogClick('Next') - cy.get('[data-cy="add-shift-expander"]').click() + cy.get(dialog).find('[data-cy="toggle-custom"]').click() // check default state of duration - cy.get(dialog) - .find('input[name="shift-end"]') - .should('have.value', defaultDurationHrs) - cy.get(dialog).find('[data-cy="toggle-duration-off"]').click() - cy.get('input[name="shift-start"]') .invoke('val') .then((shiftStart) => { @@ -55,6 +50,12 @@ function testTemporarySchedule(screen: string): void { ) }) }) + + cy.get(dialog).find('[data-cy="toggle-duration-on"]').click() + + cy.get(dialog) + .find('input[name="shift-end"]') + .should('have.value', defaultDurationHrs) }) it('should cancel and close form', () => { @@ -75,7 +76,6 @@ function testTemporarySchedule(screen: string): void { cy.dialogForm({ variant: 'temp' }) cy.dialogClick('Next') cy.dialogForm({ start: schedTZ(start), end: schedTZ(end) }) - cy.get('[data-cy="add-shift-expander"]').click() cy.get('[data-cy="no-coverage-checkbox"]').should('not.exist') cy.get('[data-cy="shifts-list"]').should('not.contain', manualAddUser.name) cy.dialogForm({ userID: manualAddUser.name }) @@ -118,10 +118,11 @@ function testTemporarySchedule(screen: string): void { graphQLAddUser.name, ) - cy.get('[data-cy="add-shift-expander"]').click() + cy.get(dialog).find('[data-cy="toggle-custom"]').click() + cy.dialogForm({ userID: manualAddUser.name, - 'shift-start': schedTZ(startTime.plus({ hour: 1 })), + 'shift-end': schedTZ(startTime.plus({ hour: 1 })), }) cy.get('[data-cy="shifts-list"]').should( 'not.contain', @@ -197,10 +198,11 @@ function testTemporarySchedule(screen: string): void { cy.get('[data-cy="edit-temp-sched"]').click() cy.get('[data-cy="shifts-list"]').should('contain', graphQLAddUser.name) - cy.get('[data-cy="add-shift-expander"]').click() + cy.get(dialog).find('[data-cy="toggle-custom"]').click() + cy.dialogForm({ userID: manualAddUser.name, - 'shift-start': schedTZ(startTime.plus({ hour: 1 })), + 'shift-end': schedTZ(startTime.plus({ hour: 1 })), }) cy.get('[data-cy="shifts-list"]').should( 'not.contain', @@ -249,13 +251,12 @@ function testTemporarySchedule(screen: string): void { start: schedTZ(ivl.start), end: schedTZ(ivl.end), }) - cy.get('[data-cy="add-shift-expander"]').click() + cy.get(dialog).find('[data-cy="toggle-custom"]').click() + cy.get(dialog).find('[data-cy="toggle-duration-on"]').click() // add first shift cy.dialogForm({ userID: manualAddUser.name, - 'shift-start': schedTZ(ivl.start), - 'shift-end': (ivl.toDuration().as('hours') / 3).toFixed(2), }) cy.get('[data-cy="shifts-list"]').should('not.contain', manualAddUser.name) cy.get('button[data-cy="add-shift"]').click() @@ -289,7 +290,6 @@ function testTemporarySchedule(screen: string): void { // click on first no coverage notice in list cy.get('[data-cy="day-no-coverage"]').eq(0).click() - cy.get('[data-cy="add-shift-container"]').should('be.visible') cy.get('input[name="shift-start"]').should( 'have.value', start.toFormat(dtFmt), @@ -298,6 +298,10 @@ function testTemporarySchedule(screen: string): void { // add shift to split up coverage for a given day const shiftStart = start.plus({ day: 1 }).toFormat(dtFmt) const duration = 2 + + cy.get(dialog).find('[data-cy="toggle-custom"]').click() + cy.get(dialog).find('[data-cy="toggle-duration-on"]').click() + cy.dialogForm({ userID: manualAddUser.name, 'shift-start': shiftStart, @@ -305,17 +309,18 @@ function testTemporarySchedule(screen: string): void { }) cy.get('button[data-cy="add-shift"]').click() + cy.get(dialog).find('[data-cy="toggle-custom"]').click() + // reset shift start field to a random value const randDate = randDTWithinInterval(Interval.fromDateTimes(start, end)) cy.dialogForm({ 'shift-start': randDate }) // click on second no coverage notice in list (partial day) cy.get('[data-cy="day-no-coverage"]').eq(1).click() - cy.get('[data-cy="add-shift-container"]').should('be.visible') const shiftEnd = start.plus({ day: 1, hours: duration }).toFormat(dtFmt) cy.get('input[name="shift-start"]').should('have.value', shiftEnd) - cy.get('input[name="shift-end"]').should('have.value', duration) // ensure duration remains the same + cy.get('input[name="shift-end"]').should('have.value', 24) // ensure duration remains the fixed duration }) } From 504db4b2ded2fc8a4813bb78f13396573319ee73 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 12:46:12 -0600 Subject: [PATCH 11/18] build(deps): bump github.com/99designs/gqlgen from 0.17.42 to 0.17.43 (#3616) Bumps [github.com/99designs/gqlgen](https://github.com/99designs/gqlgen) from 0.17.42 to 0.17.43. - [Release notes](https://github.com/99designs/gqlgen/releases) - [Changelog](https://github.com/99designs/gqlgen/blob/master/CHANGELOG.md) - [Commits](https://github.com/99designs/gqlgen/compare/v0.17.42...v0.17.43) --- updated-dependencies: - dependency-name: github.com/99designs/gqlgen dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e3c2a2693a..75bb27a81c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/target/goalert go 1.21 require ( - github.com/99designs/gqlgen v0.17.42 + github.com/99designs/gqlgen v0.17.43 github.com/brianvoe/gofakeit/v6 v6.28.0 github.com/coreos/go-oidc/v3 v3.9.0 github.com/creack/pty/v2 v2.0.1 diff --git a/go.sum b/go.sum index 3e8d2fa728..87bb6cb788 100644 --- a/go.sum +++ b/go.sum @@ -598,8 +598,8 @@ cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcP dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= -github.com/99designs/gqlgen v0.17.42 h1:BVWDOb2VVHQC5k3m6oa0XhDnxltLLrU4so7x/u39Zu4= -github.com/99designs/gqlgen v0.17.42/go.mod h1:GQ6SyMhwFbgHR0a8r2Wn8fYgEwPxxmndLFPhU63+cJE= +github.com/99designs/gqlgen v0.17.43 h1:I4SYg6ahjowErAQcHFVKy5EcWuwJ3+Xw9z2fLpuFCPo= +github.com/99designs/gqlgen v0.17.43/go.mod h1:lO0Zjy8MkZgBdv4T1U91x09r0e0WFOdhVUutlQs1Rsc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= From dc43aaaaf08e1e48b5ba5ffedcd11fda55a01c16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 13:40:07 -0600 Subject: [PATCH 12/18] build(deps-dev): bump prettier from 3.1.0 to 3.2.4 (#3614) * build(deps-dev): bump prettier from 3.1.0 to 3.2.4 Bumps [prettier](https://github.com/prettier/prettier) from 3.1.0 to 3.2.4. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.1.0...3.2.4) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * yarn run lint --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nathaniel Cook --- package.json | 2 +- tsconfig.json | 4 ++-- .../admin/admin-service-metrics/useServiceMetrics.ts | 4 ++-- web/src/app/tsconfig.json | 4 ++-- web/src/cypress/tsconfig.json | 4 ++-- web/src/explore/tsconfig.json | 4 ++-- web/src/jsconfig.json | 4 ++-- yarn.lock | 10 +++++----- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 3d03fe7567..058f4866cf 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "mdi-material-ui": "7.7.0", "msw": "2.0.11", "msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0", - "prettier": "3.1.0", + "prettier": "3.2.4", "prettier-plugin-go-template": "0.0.15", "prop-types": "15.8.1", "punycode": "2.3.1", diff --git a/tsconfig.json b/tsconfig.json index e682c65ae0..7716897f19 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "noUnusedLocals": true, "skipLibCheck": true, "strict": true, - "lib": ["ES2017"] + "lib": ["ES2017"], }, - "exclude": ["*/node_modules/*"] + "exclude": ["*/node_modules/*"], } diff --git a/web/src/app/admin/admin-service-metrics/useServiceMetrics.ts b/web/src/app/admin/admin-service-metrics/useServiceMetrics.ts index da308c734d..b68d31c8ae 100644 --- a/web/src/app/admin/admin-service-metrics/useServiceMetrics.ts +++ b/web/src/app/admin/admin-service-metrics/useServiceMetrics.ts @@ -45,8 +45,8 @@ export function useServiceMetrics(opts: ServiceMetricOpts): ServiceMetrics { if (!stepTargetMatch) return false } if (filters?.intKeyTgts?.length) { - const intKeyMatch = svc.integrationKeys.some( - (key) => filters.intKeyTgts?.includes(key.type), + const intKeyMatch = svc.integrationKeys.some((key) => + filters.intKeyTgts?.includes(key.type), ) if (!intKeyMatch) return false } diff --git a/web/src/app/tsconfig.json b/web/src/app/tsconfig.json index 588d79ebca..6635556d89 100644 --- a/web/src/app/tsconfig.json +++ b/web/src/app/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../../tsconfig.json", "compilerOptions": { "jsx": "react", - "lib": ["es6", "dom"] + "lib": ["es6", "dom"], }, - "include": ["**/*", "../*.d.ts"] + "include": ["**/*", "../*.d.ts"], } diff --git a/web/src/cypress/tsconfig.json b/web/src/cypress/tsconfig.json index c55ed3e5df..3112724845 100644 --- a/web/src/cypress/tsconfig.json +++ b/web/src/cypress/tsconfig.json @@ -2,6 +2,6 @@ "extends": "../app/tsconfig", "include": ["**/*"], "compilerOptions": { - "resolveJsonModule": true - } + "resolveJsonModule": true, + }, } diff --git a/web/src/explore/tsconfig.json b/web/src/explore/tsconfig.json index 588d79ebca..6635556d89 100644 --- a/web/src/explore/tsconfig.json +++ b/web/src/explore/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../../tsconfig.json", "compilerOptions": { "jsx": "react", - "lib": ["es6", "dom"] + "lib": ["es6", "dom"], }, - "include": ["**/*", "../*.d.ts"] + "include": ["**/*", "../*.d.ts"], } diff --git a/web/src/jsconfig.json b/web/src/jsconfig.json index 8212384765..a73cd78def 100644 --- a/web/src/jsconfig.json +++ b/web/src/jsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "target": "ES6", - "module": "commonjs" + "module": "commonjs", }, - "exclude": ["node_modules", "dist", "build"] + "exclude": ["node_modules", "dist", "build"], } diff --git a/yarn.lock b/yarn.lock index b9c5d6d9e6..0502fd0f47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16157,12 +16157,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:3.1.0": - version: 3.1.0 - resolution: "prettier@npm:3.1.0" +"prettier@npm:3.2.4": + version: 3.2.4 + resolution: "prettier@npm:3.2.4" bin: prettier: bin/prettier.cjs - checksum: 44b556bd56f74d7410974fbb2418bb4e53a894d3e7b42f6f87779f69f27a6c272fa7fc27cec0118cd11730ef3246478052e002cbd87e9a253f9cd04a56aa7d9b + checksum: 6ec9385a836e0b9bac549e585101c086d1521c31d7b882d5c8bb7d7646da0693da5f31f4fff6dc080710e5e2d34c85e6fb2f8766876b3645c8be2f33b9c3d1a3 languageName: node linkType: hard @@ -17626,7 +17626,7 @@ __metadata: mdi-material-ui: 7.7.0 msw: 2.0.11 msw-storybook-addon: 2.0.0--canary.122.b3ed3b1.0 - prettier: 3.1.0 + prettier: 3.2.4 prettier-plugin-go-template: 0.0.15 prop-types: 15.8.1 punycode: 2.3.1 From 280034b3e10eaa6d8c7bcbe88dc54321619e57df Mon Sep 17 00:00:00 2001 From: Tony Vu Date: Mon, 22 Jan 2024 13:40:49 -0600 Subject: [PATCH 13/18] selection: add DestinationField component and tests (#3592) * add destination field component and tests * fix enabled value * add field-specific errors * capitalize * update error message * fix multi-field spacing * fix test meta options * add comment to list possible fieldIDs * remove fieldIDs list comment * make stacked * update test * use experimental flag for RequireConfigs provider --------- Co-authored-by: TonyTVu Co-authored-by: Nathaniel Caza --- .../selection/DestinationField.stories.tsx | 153 ++++++++++++++++++ web/src/app/selection/DestinationField.tsx | 86 ++++++++++ .../app/selection/DestinationInputDirect.tsx | 2 + web/src/app/storybook/defaultDestTypes.ts | 106 ++++++++++++ web/src/app/storybook/graphql.ts | 4 + web/src/app/util/RequireConfig.tsx | 67 +++++++- 6 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 web/src/app/selection/DestinationField.stories.tsx create mode 100644 web/src/app/selection/DestinationField.tsx create mode 100644 web/src/app/storybook/defaultDestTypes.ts diff --git a/web/src/app/selection/DestinationField.stories.tsx b/web/src/app/selection/DestinationField.stories.tsx new file mode 100644 index 0000000000..6bfad42b17 --- /dev/null +++ b/web/src/app/selection/DestinationField.stories.tsx @@ -0,0 +1,153 @@ +import React from 'react' +import type { Meta, StoryObj } from '@storybook/react' +import DestinationField from './DestinationField' +import { expect } from '@storybook/jest' +import { within } from '@storybook/testing-library' +import { handleDefaultConfig } from '../storybook/graphql' +import { useArgs } from '@storybook/preview-api' +import { FieldValueInput } from '../../schema' + +const meta = { + title: 'util/DestinationField', + component: DestinationField, + tags: ['autodocs'], + argTypes: { + destType: { + control: 'select', + options: ['single-field', 'triple-field', 'disabled-destination'], + }, + }, + parameters: { + msw: { + handlers: [handleDefaultConfig], + }, + }, + render: function Component(args) { + const [, setArgs] = useArgs() + const onChange = (newValue: FieldValueInput[]): void => { + if (args.onChange) args.onChange(newValue) + setArgs({ value: newValue }) + } + return + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const SingleField: Story = { + args: { + destType: 'single-field', + value: [ + { + fieldID: 'phone-number', + value: '', + }, + ], + disabled: false, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement) + + // ensure information renders correctly + await expect(canvas.getByLabelText('Phone Number')).toBeVisible() + await expect( + canvas.getByText( + 'Include country code e.g. +1 (USA), +91 (India), +44 (UK)', + ), + ).toBeVisible() + await expect(canvas.getByText('+')).toBeVisible() + await expect(canvas.getByPlaceholderText('11235550123')).toBeVisible() + }, +} + +export const MultiField: Story = { + args: { + destType: 'triple-field', + value: [ + { + fieldID: 'first-field', + value: '', + }, + { + fieldID: 'second-field', + value: 'test@example.com', + }, + { + fieldID: 'third-field', + value: '', + }, + ], + disabled: false, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement) + + // ensure information for phone number renders correctly + await expect(canvas.getByLabelText('First Item')).toBeVisible() + await expect(canvas.getByText('Some hint text')).toBeVisible() + await expect(canvas.getByText('+')).toBeVisible() + await expect(canvas.getByPlaceholderText('11235550123')).toBeVisible() + + // ensure information for email renders correctly + await expect( + canvas.getByPlaceholderText('foobar@example.com'), + ).toBeVisible() + await expect(canvas.getByLabelText('Second Item')).toBeVisible() + + // ensure information for slack renders correctly + await expect(canvas.getByPlaceholderText('slack user ID')).toBeVisible() + await expect(canvas.getByLabelText('Third Item')).toBeVisible() + }, +} + +export const DisabledField: Story = { + args: { + destType: 'disabled-destination', + value: [ + { + fieldID: 'disabled', + value: '', + }, + ], + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement) + + // ensure information renders correctly + await expect( + canvas.getByPlaceholderText('This field is disabled.'), + ).toBeVisible() + }, +} + +export const FieldError: Story = { + args: { + destType: 'triple-field', + value: [ + { + fieldID: 'first-field', + value: '', + }, + { + fieldID: 'second-field', + value: 'test@example.com', + }, + { + fieldID: 'third-field', + value: '', + }, + ], + disabled: false, + destFieldErrors: [ + { + fieldID: 'third-field', + message: 'This is an error message (third)', + }, + { + fieldID: 'first-field', + message: 'This is an error message (first)', + }, + ], + }, +} diff --git a/web/src/app/selection/DestinationField.tsx b/web/src/app/selection/DestinationField.tsx new file mode 100644 index 0000000000..0a5bcf1fc5 --- /dev/null +++ b/web/src/app/selection/DestinationField.tsx @@ -0,0 +1,86 @@ +import React from 'react' +import { DestinationType, FieldValueInput } from '../../schema' +import DestinationInputDirect from './DestinationInputDirect' +import { useDestinationType } from '../util/RequireConfig' +import DestinationSearchSelect from './DestinationSearchSelect' +import { Grid } from '@mui/material' + +export type DestinationFieldProps = { + value: FieldValueInput[] + onChange?: (value: FieldValueInput[]) => void + destType: DestinationType + + disabled?: boolean + + destFieldErrors?: DestFieldError[] +} + +export interface DestFieldError { + fieldID: string + message: string +} + +export default function DestinationField( + props: DestinationFieldProps, +): React.ReactNode { + const dest = useDestinationType(props.destType) + + return ( + + {dest.requiredFields.map((field) => { + const fieldValue = + (props.value || []).find((v) => v.fieldID === field.fieldID)?.value || + '' + + function handleChange(newValue: string): void { + if (!props.onChange) return + + const newValues = (props.value || []) + .filter((v) => v.fieldID !== field.fieldID) + .concat({ + fieldID: field.fieldID, + value: newValue, + }) + + props.onChange(newValues) + } + + const fieldErrMsg = + props.destFieldErrors?.find((err) => err.fieldID === field.fieldID) + ?.message || '' + + if (field.isSearchSelectable) + return ( + + handleChange(val)} + error={!!fieldErrMsg} + hint={fieldErrMsg || field.hint} + hintURL={fieldErrMsg ? '' : field.hintURL} + /> + + ) + + return ( + + handleChange(e.target.value)} + error={!!fieldErrMsg} + hint={fieldErrMsg || field.hint} + hintURL={fieldErrMsg ? '' : field.hintURL} + /> + + ) + })} + + ) + return +} diff --git a/web/src/app/selection/DestinationInputDirect.tsx b/web/src/app/selection/DestinationInputDirect.tsx index b9bdac2c74..81e96d0c37 100644 --- a/web/src/app/selection/DestinationInputDirect.tsx +++ b/web/src/app/selection/DestinationInputDirect.tsx @@ -30,6 +30,7 @@ export type DestinationInputDirectProps = DestinationFieldConfig & { destType: DestinationType disabled?: boolean + error?: boolean } /** @@ -132,6 +133,7 @@ export default function DestinationInputDirect( } onChange={handleChange} value={trimPrefix(props.value, props.prefix)} + error={props.error} /> ) } diff --git a/web/src/app/storybook/defaultDestTypes.ts b/web/src/app/storybook/defaultDestTypes.ts new file mode 100644 index 0000000000..62c1acfa65 --- /dev/null +++ b/web/src/app/storybook/defaultDestTypes.ts @@ -0,0 +1,106 @@ +import { DestinationTypeInfo } from '../../schema' + +export const destTypes: DestinationTypeInfo[] = [ + { + type: 'single-field', + name: 'Single Field Destination Type', + enabled: true, + disabledMessage: 'Single field destination type must be configured.', + userDisclaimer: '', + isContactMethod: true, + isEPTarget: false, + isSchedOnCallNotify: false, + iconURL: '', + iconAltText: '', + requiredFields: [ + { + fieldID: 'phone-number', + labelSingular: 'Phone Number', + labelPlural: 'Phone Numbers', + hint: 'Include country code e.g. +1 (USA), +91 (India), +44 (UK)', + hintURL: '', + placeholderText: '11235550123', + prefix: '+', + inputType: 'tel', + isSearchSelectable: false, + supportsValidation: true, + }, + ], + }, + { + type: 'triple-field', + name: 'Multi Field Destination Type', + enabled: true, + disabledMessage: 'Multi field destination type must be configured.', + userDisclaimer: '', + isContactMethod: true, + isEPTarget: false, + isSchedOnCallNotify: false, + iconURL: '', + iconAltText: '', + requiredFields: [ + { + fieldID: 'first-field', + labelSingular: 'First Item', + labelPlural: 'First Items', + hint: 'Some hint text', + hintURL: '', + placeholderText: '11235550123', + prefix: '+', + inputType: 'tel', + isSearchSelectable: false, + supportsValidation: true, + }, + { + fieldID: 'second-field', + labelSingular: 'Second Item', + labelPlural: 'Second Items', + hint: '', + hintURL: '', + placeholderText: 'foobar@example.com', + prefix: '', + inputType: 'email', + isSearchSelectable: false, + supportsValidation: true, + }, + { + fieldID: 'third-field', + labelSingular: 'Third Item', + labelPlural: 'Third Items', + hint: '', + hintURL: '', + placeholderText: 'slack user ID', + prefix: '', + inputType: 'string', + isSearchSelectable: false, + supportsValidation: true, + }, + ], + }, + { + type: 'disabled-destination', + name: 'This is disabled', + enabled: false, + disabledMessage: 'This field is disabled.', + userDisclaimer: '', + isContactMethod: true, + isEPTarget: true, + isSchedOnCallNotify: true, + iconURL: '', + iconAltText: '', + requiredFields: [ + { + fieldID: 'disabled', + labelSingular: '', + labelPlural: '', + hint: '', + hintURL: '', + placeholderText: 'This field is disabled.', + prefix: '', + inputType: 'url', + isSearchSelectable: false, + supportsValidation: false, + }, + ], + }, +] diff --git a/web/src/app/storybook/graphql.ts b/web/src/app/storybook/graphql.ts index 8f7459988e..abdf68b40a 100644 --- a/web/src/app/storybook/graphql.ts +++ b/web/src/app/storybook/graphql.ts @@ -2,9 +2,11 @@ import { GraphQLHandler, HttpResponse, graphql } from 'msw' import { ConfigID, ConfigType, + DestinationTypeInfo, IntegrationKeyTypeInfo, UserRole, } from '../../schema' +import { destTypes } from './defaultDestTypes' export type ConfigItem = { id: ConfigID @@ -20,6 +22,7 @@ export type RequireConfigDoc = { } config: ConfigItem[] integrationKeyTypes: IntegrationKeyTypeInfo[] + destinationTypes: DestinationTypeInfo[] } export function handleConfig(doc: RequireConfigDoc): GraphQLHandler { @@ -57,6 +60,7 @@ export const defaultConfig: RequireConfigDoc = { enabled: false, }, ], + destinationTypes: destTypes, } export const handleDefaultConfig = handleConfig(defaultConfig) diff --git a/web/src/app/util/RequireConfig.tsx b/web/src/app/util/RequireConfig.tsx index 439c3bbddf..ba92753cf4 100644 --- a/web/src/app/util/RequireConfig.tsx +++ b/web/src/app/util/RequireConfig.tsx @@ -5,7 +5,10 @@ import { ConfigValue, ConfigID, IntegrationKeyTypeInfo, + DestinationTypeInfo, + DestinationType, } from '../../schema' +import { useExpFlag } from './useExpFlag' type Value = boolean | number | string | string[] | null export type ConfigData = Record @@ -16,6 +19,7 @@ const ConfigContext = React.createContext({ isAdmin: false as boolean, userID: '' as string, userName: null as string | null, + destTypes: [] as DestinationTypeInfo[], }) ConfigContext.displayName = 'ConfigContext' @@ -40,12 +44,62 @@ const query = gql` } ` +// expDestQuery will be used when "dest-types" experimental flag is enabled. +// expDestQuery should replace "query" when new components have been fully integrated into master branch for https://github.com/target/goalert/issues/2639 +const expDestQuery = gql` + query RequireConfig { + user { + id + name + role + } + config { + id + type + value + } + integrationKeyTypes { + id + name + label + enabled + } + destinationTypes { + type + name + enabled + disabledMessage + userDisclaimer + + isContactMethod + isEPTarget + isSchedOnCallNotify + + requiredFields { + fieldID + labelSingular + labelPlural + hint + hintURL + placeholderText + prefix + inputType + isSearchSelectable + supportsValidation + } + } + } +` + type ConfigProviderProps = { children: ReactChild | ReactChild[] } export function ConfigProvider(props: ConfigProviderProps): React.ReactNode { - const [{ data }] = useQuery({ query }) + const hasDestTypesFlag = useExpFlag('dest-types') + const [{ data }] = useQuery({ + query: hasDestTypesFlag ? expDestQuery : query, + }) return ( {props.children} @@ -154,6 +209,16 @@ export function useConfigValue(...fields: ConfigID[]): Value[] { return fields.map((f) => config[f]) } +// useDestinationType returns information about the given destination type. +export function useDestinationType(type: DestinationType): DestinationTypeInfo { + const ctx = React.useContext(ConfigContext) + const typeInfo = ctx.destTypes.find((t) => t.type === type) + + if (!typeInfo) throw new Error(`unknown destination type '${type}'`) + + return typeInfo +} + export function Config(props: { children: (x: ConfigData, s?: SessionInfo) => JSX.Element }): JSX.Element { From 2ab18e94dbeac0035628b4aeeed990d0b1723198 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 22 Jan 2024 13:41:05 -0600 Subject: [PATCH 14/18] prometheus: add service_id to created alerts and sent notification counters (#3603) * breakdown created alerts by service id * msg sent by service id * track status messages by service id --- alert/metrics.go | 4 ++-- alert/store.go | 4 ++-- engine/sendmessage.go | 1 + notification/alertstatus.go | 1 + notification/manager.go | 15 ++++++++++++++- notification/metrics.go | 2 +- 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/alert/metrics.go b/alert/metrics.go index 2cfee32136..b341dba49f 100644 --- a/alert/metrics.go +++ b/alert/metrics.go @@ -6,10 +6,10 @@ import ( ) var ( - metricCreatedTotal = promauto.NewCounter(prometheus.CounterOpts{ + metricCreatedTotal = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: "goalert", Subsystem: "alert", Name: "created_total", Help: "The total number of created alerts.", - }) + }, []string{"service_id"}) ) diff --git a/alert/store.go b/alert/store.go index c84944b627..416878843d 100644 --- a/alert/store.go +++ b/alert/store.go @@ -514,7 +514,7 @@ func (s *Store) Create(ctx context.Context, a *Alert) (*Alert, error) { ctx = log.WithFields(ctx, log.Fields{"AlertID": n.ID, "ServiceID": n.ServiceID}) log.Logf(ctx, "Alert created.") - metricCreatedTotal.Inc() + metricCreatedTotal.WithLabelValues(n.ServiceID).Inc() return n, nil } @@ -652,7 +652,7 @@ func (s *Store) CreateOrUpdate(ctx context.Context, a *Alert) (*Alert, bool, err if isNew { ctx = log.WithFields(ctx, log.Fields{"AlertID": n.ID, "ServiceID": n.ServiceID}) log.Logf(ctx, "Alert created.") - metricCreatedTotal.Inc() + metricCreatedTotal.WithLabelValues(n.ServiceID).Inc() } return n, isNew, nil diff --git a/engine/sendmessage.go b/engine/sendmessage.go index 60c3e3606d..96ad9aad98 100644 --- a/engine/sendmessage.go +++ b/engine/sendmessage.go @@ -113,6 +113,7 @@ func (p *Engine) sendMessage(ctx context.Context, msg *message.Message) (*notifi notifMsg = notification.AlertStatus{ Dest: msg.Dest, AlertID: e.AlertID(), + ServiceID: a.ServiceID, CallbackID: msg.ID, LogEntry: e.String(ctx), Summary: a.Summary, diff --git a/notification/alertstatus.go b/notification/alertstatus.go index 4793c022e5..db3bcf5bfb 100644 --- a/notification/alertstatus.go +++ b/notification/alertstatus.go @@ -16,6 +16,7 @@ type AlertStatus struct { CallbackID string AlertID int LogEntry string + ServiceID string // Summary of the alert that this status is in regards to. Summary string diff --git a/notification/manager.go b/notification/manager.go index 0b1f8edf82..ef2663801c 100644 --- a/notification/manager.go +++ b/notification/manager.go @@ -150,7 +150,7 @@ func (mgr *Manager) SendMessage(ctx context.Context, msg Message) (*SendResult, } log.Logf(sendCtx, "notification sent") metricSentTotal. - WithLabelValues(msg.Destination().Type.String(), msg.Type().String()). + WithLabelValues(msg.Destination().Type.String(), msg.Type().String(), msgSvcID(msg)). Inc() // status already wrapped via namedSender return res, nil @@ -161,3 +161,16 @@ func (mgr *Manager) SendMessage(ctx context.Context, msg Message) (*SendResult, return nil, errors.New("all notification senders failed") } + +func msgSvcID(msg Message) string { + switch msg := msg.(type) { + case Alert: + return msg.ServiceID + case AlertBundle: + return msg.ServiceID + case AlertStatus: + return msg.ServiceID + } + + return "" +} diff --git a/notification/metrics.go b/notification/metrics.go index cb6da55ba6..cd15cab251 100644 --- a/notification/metrics.go +++ b/notification/metrics.go @@ -11,7 +11,7 @@ var ( Subsystem: "notification", Name: "sent_total", Help: "Total number of sent notifications.", - }, []string{"dest_type", "message_type"}) + }, []string{"dest_type", "message_type", "service_id"}) metricRecvTotal = promauto.NewCounterVec(prometheus.CounterOpts{ Namespace: "goalert", Subsystem: "notification", From a18db16ff55074e73cd458cc7339635633f01c01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 14:47:44 -0600 Subject: [PATCH 15/18] build(deps): bump github.com/jackc/pgtype from 1.14.0 to 1.14.1 (#3619) Bumps [github.com/jackc/pgtype](https://github.com/jackc/pgtype) from 1.14.0 to 1.14.1. - [Changelog](https://github.com/jackc/pgtype/blob/master/CHANGELOG.md) - [Commits](https://github.com/jackc/pgtype/compare/v1.14.0...v1.14.1) --- updated-dependencies: - dependency-name: github.com/jackc/pgtype dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 75bb27a81c..38276ae04d 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/google/uuid v1.5.0 github.com/gordonklaus/ineffassign v0.1.0 github.com/hashicorp/yamux v0.1.1 - github.com/jackc/pgtype v1.14.0 + github.com/jackc/pgtype v1.14.1 github.com/jackc/pgx/v5 v5.5.2 github.com/jmespath/go-jmespath v0.4.0 github.com/joho/godotenv v1.5.1 diff --git a/go.sum b/go.sum index 87bb6cb788..8b31f420d8 100644 --- a/go.sum +++ b/go.sum @@ -959,8 +959,9 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.14.1 h1:LyDar7M2K0tShCWqzJ/ctzF1QC3Wzc9c8a6cHE0PFdc= +github.com/jackc/pgtype v1.14.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= From e005aec01019503a0f42bf36d1d35848fe7c217e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 08:46:12 -0600 Subject: [PATCH 16/18] build(deps-dev): bump @mui/material from 5.14.10 to 5.15.5 (#3615) * build(deps-dev): bump @mui/material from 5.14.10 to 5.15.5 Bumps [@mui/material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-material) from 5.14.10 to 5.15.5. - [Release notes](https://github.com/mui/material-ui/releases) - [Changelog](https://github.com/mui/material-ui/blob/master/CHANGELOG.md) - [Commits](https://github.com/mui/material-ui/commits/v5.15.5/packages/mui-material) --- updated-dependencies: - dependency-name: "@mui/material" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * update styles * fix int key create * update material select cypress getter * check for loading on selects * fix bad test * fix material select identifier * clear --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nathaniel Cook Co-authored-by: Nathaniel Caza --- package.json | 4 +- test/integration/service.spec.ts | 2 +- .../cypress/e2e/calendarSubscriptions.cy.ts | 1 + web/src/cypress/support/form.ts | 19 +-- web/src/cypress/support/select-by-label.ts | 8 +- yarn.lock | 122 +++++++++++++----- 6 files changed, 110 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index 058f4866cf..e62dc3ec24 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "@material/material-color-utilities": "0.2.7", "@mui/icons-material": "5.14.9", "@mui/lab": "5.0.0-alpha.145", - "@mui/material": "5.14.10", - "@mui/styles": "5.14.16", + "@mui/material": "5.15.5", + "@mui/styles": "5.15.5", "@mui/system": "5.15.5", "@mui/x-data-grid": "6.18.7", "@playwright/test": "1.40.1", diff --git a/test/integration/service.spec.ts b/test/integration/service.spec.ts index 256ee89665..8a67f731ac 100644 --- a/test/integration/service.spec.ts +++ b/test/integration/service.spec.ts @@ -247,7 +247,7 @@ test('Service', async ({ page, isMobile }) => { await page.getByTestId('create-key').click() } await page.getByLabel('Name').fill(grafanaKey) - await page.getByRole('button', { name: 'Type Generic API' }).click() + await page.getByRole('combobox', { name: 'Generic API' }).click() await page.getByRole('option', { name: 'Grafana' }).click() await page.getByRole('button', { name: 'Submit' }).click() diff --git a/web/src/cypress/e2e/calendarSubscriptions.cy.ts b/web/src/cypress/e2e/calendarSubscriptions.cy.ts index 347d8d2bf1..83156b3b50 100644 --- a/web/src/cypress/e2e/calendarSubscriptions.cy.ts +++ b/web/src/cypress/e2e/calendarSubscriptions.cy.ts @@ -39,6 +39,7 @@ function testSubs(screen: ScreenFormat): void { cy.dialogContains(Cypress.config().baseUrl + '/api/v2/calendar?token=') cy.dialogFinish('Done') + cy.get('[data-cy="subscribe-btn"]').trigger('mouseover') cy.get('body').should('not.contain', defaultCptn) cy.get('body').should('contain', 'You have 1 active subscription') }) diff --git a/web/src/cypress/support/form.ts b/web/src/cypress/support/form.ts index 989b0b6397..e247d9ebe9 100644 --- a/web/src/cypress/support/form.ts +++ b/web/src/cypress/support/form.ts @@ -41,16 +41,15 @@ function fillFormField( return cy.get(selector).check() } - const isSelect = - el.parents('[data-cy=material-select]').data('cy') === - 'material-select' || - el.siblings('[role=button]').attr('aria-haspopup') === 'listbox' - - if (isSelect) { + // select dropdowns + if ( + el.attr('role') === 'combobox' || + el.siblings().attr('aria-haspopup') === 'listbox' + ) { + // action to clear multi-select if (value === '') { cy.get(selector).clear() - // clear chips on multi-select el.parent() .find('[data-cy="multi-value"]') .each(() => { @@ -78,8 +77,11 @@ function fillFormField( throw new TypeError('arrays only supported for search-select inputs') } - if (value === '') return cy.get(selector).clear() + if (value === '') { + return cy.get(selector).clear() + } + // everything else return cy.get(selector).then((el) => { if (!DateTime.isDateTime(value)) { if (el.attr('type') === 'hidden') { @@ -90,7 +92,6 @@ function fillFormField( } cy.wrap(el).clear() - // material Select switch (el.attr('type')) { case 'time': return cy.focused().type(value.toFormat('HH:mm')) diff --git a/web/src/cypress/support/select-by-label.ts b/web/src/cypress/support/select-by-label.ts index 5e63605476..b834331974 100644 --- a/web/src/cypress/support/select-by-label.ts +++ b/web/src/cypress/support/select-by-label.ts @@ -61,15 +61,17 @@ function findByLabel( .click() cy.focused().should('be.visible').type(label) - - cy.get('[data-cy=select-dropdown]').should('not.contain', 'Loading') + cy.get('[data-cy=select-dropdown]').should( + 'not.contain.text', + 'Loading...', + ) return cy .get('body') .contains('[data-cy=select-dropdown] [role=option]', label) } - cy.wrap(sub).parent().find('[role=button]').click() + cy.wrap(sub).parent().find('[role=combobox]').click() return cy.get('ul[role=listbox]').contains('li', label) }) diff --git a/yarn.lock b/yarn.lock index 0502fd0f47..9722e46404 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2320,6 +2320,15 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.5.3": + version: 1.5.3 + resolution: "@floating-ui/core@npm:1.5.3" + dependencies: + "@floating-ui/utils": ^0.2.0 + checksum: 72af8563e1742791acee82e86f82a0fbca7445809988d31eea3fd5771909463aa7655a6cb001cc244f8fe3a9de600420257e4dfb887ca33e2a31ac47b52e39a2 + languageName: node + linkType: hard + "@floating-ui/dom@npm:^1.5.1": version: 1.5.3 resolution: "@floating-ui/dom@npm:1.5.3" @@ -2330,6 +2339,16 @@ __metadata: languageName: node linkType: hard +"@floating-ui/dom@npm:^1.5.4": + version: 1.5.4 + resolution: "@floating-ui/dom@npm:1.5.4" + dependencies: + "@floating-ui/core": ^1.5.3 + "@floating-ui/utils": ^0.2.0 + checksum: 5e6f05532ff4e6daf9f2d91534184d8f942ddb8fd260c2543a49bdf0c0ff69fd0867937ce1d023126008724ac238f8fc89b5e48f82cdf9f8355a1d04edd085bd + languageName: node + linkType: hard + "@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.0.2": version: 2.0.4 resolution: "@floating-ui/react-dom@npm:2.0.4" @@ -2342,6 +2361,18 @@ __metadata: languageName: node linkType: hard +"@floating-ui/react-dom@npm:^2.0.5": + version: 2.0.6 + resolution: "@floating-ui/react-dom@npm:2.0.6" + dependencies: + "@floating-ui/dom": ^1.5.4 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 3608537be6cae5f0442d3f826379b8e4a9ce5c4bdecf1d2b34e6709842d80444be1a00eca3641d680e2e6405d833092f58005d1b120a9d39ffd341c60b0c017c + languageName: node + linkType: hard + "@floating-ui/utils@npm:^0.1.3": version: 0.1.6 resolution: "@floating-ui/utils@npm:0.1.6" @@ -2349,6 +2380,13 @@ __metadata: languageName: node linkType: hard +"@floating-ui/utils@npm:^0.2.0": + version: 0.2.1 + resolution: "@floating-ui/utils@npm:0.2.1" + checksum: 9ed4380653c7c217cd6f66ae51f20fdce433730dbc77f95b5abfb5a808f5fdb029c6ae249b4e0490a816f2453aa6e586d9a873cd157fdba4690f65628efc6e06 + languageName: node + linkType: hard + "@graphiql/react@npm:^0.20.2": version: 0.20.2 resolution: "@graphiql/react@npm:0.20.2" @@ -2979,10 +3017,32 @@ __metadata: languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.14.10": - version: 5.15.2 - resolution: "@mui/core-downloads-tracker@npm:5.15.2" - checksum: 8c88ac73a1d87c8ce565f6295dcd084c643580848e8f59159402e9db89975263da06305a0e605d3744479e917c2d297319496534bca9df8338e203162f1e7c33 +"@mui/base@npm:5.0.0-beta.32": + version: 5.0.0-beta.32 + resolution: "@mui/base@npm:5.0.0-beta.32" + dependencies: + "@babel/runtime": ^7.23.8 + "@floating-ui/react-dom": ^2.0.5 + "@mui/types": ^7.2.13 + "@mui/utils": ^5.15.5 + "@popperjs/core": ^2.11.8 + clsx: ^2.1.0 + prop-types: ^15.8.1 + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 5f27be8914c072ffcbe6720de9aa6129180e68927657e8bcbc03a6f322d1ee6c6740a199d72ed0b490a7b29b79cc0c59d1e05a427089b17f4cbc9cc756e67506 + languageName: node + linkType: hard + +"@mui/core-downloads-tracker@npm:^5.15.5": + version: 5.15.5 + resolution: "@mui/core-downloads-tracker@npm:5.15.5" + checksum: 4c9b1281ebe8d17d402e22f7f50c347c0b3918b1ed17af721f4de5ce282d90bc6d90fe9730595998b2bbb2f7ebe57fc55d4c858f31754fccdb606af472a59dc8 languageName: node linkType: hard @@ -3032,18 +3092,18 @@ __metadata: languageName: node linkType: hard -"@mui/material@npm:5.14.10": - version: 5.14.10 - resolution: "@mui/material@npm:5.14.10" +"@mui/material@npm:5.15.5": + version: 5.15.5 + resolution: "@mui/material@npm:5.15.5" dependencies: - "@babel/runtime": ^7.22.15 - "@mui/base": 5.0.0-beta.16 - "@mui/core-downloads-tracker": ^5.14.10 - "@mui/system": ^5.14.10 - "@mui/types": ^7.2.4 - "@mui/utils": ^5.14.10 - "@types/react-transition-group": ^4.4.6 - clsx: ^2.0.0 + "@babel/runtime": ^7.23.8 + "@mui/base": 5.0.0-beta.32 + "@mui/core-downloads-tracker": ^5.15.5 + "@mui/system": ^5.15.5 + "@mui/types": ^7.2.13 + "@mui/utils": ^5.15.5 + "@types/react-transition-group": ^4.4.10 + clsx: ^2.1.0 csstype: ^3.1.2 prop-types: ^15.8.1 react-is: ^18.2.0 @@ -3061,11 +3121,11 @@ __metadata: optional: true "@types/react": optional: true - checksum: a093affbf25826e0932c3a07483b4c335bb54a7d5e84f563ed2ca861e8e6fb3e6385164fc29a9c8e55ad59be65b1fda06ca7ce9c7221e1d2e84436ffae8415f6 + checksum: dbfcb31810c674d9ab3b9145752433de3917d9c0d1b491bdff84c44b8f1124e8fe8ab04fa09b974b497983b7bd3011b86fb441ad365f979f971d3ddb46712060 languageName: node linkType: hard -"@mui/private-theming@npm:^5.14.16, @mui/private-theming@npm:^5.15.2": +"@mui/private-theming@npm:^5.15.2": version: 5.15.2 resolution: "@mui/private-theming@npm:5.15.2" dependencies: @@ -3141,16 +3201,16 @@ __metadata: languageName: node linkType: hard -"@mui/styles@npm:5.14.16": - version: 5.14.16 - resolution: "@mui/styles@npm:5.14.16" +"@mui/styles@npm:5.15.5": + version: 5.15.5 + resolution: "@mui/styles@npm:5.15.5" dependencies: - "@babel/runtime": ^7.23.2 + "@babel/runtime": ^7.23.8 "@emotion/hash": ^0.9.1 - "@mui/private-theming": ^5.14.16 - "@mui/types": ^7.2.8 - "@mui/utils": ^5.14.16 - clsx: ^2.0.0 + "@mui/private-theming": ^5.15.5 + "@mui/types": ^7.2.13 + "@mui/utils": ^5.15.5 + clsx: ^2.1.0 csstype: ^3.1.2 hoist-non-react-statics: ^3.3.2 jss: ^10.10.0 @@ -3168,11 +3228,11 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: f30e7abeec3c4f6c5694047717b998c88eb0eb94cb0f8bcebc854eaa83b0e7a9ea40d48a81a6d44b98af2cf055ed9f4530e367a7e3fa9ebfa7bffd5418b9fbfa + checksum: dc131c0e7763f7ac4fd96a1db3dd85abf59de5429a73f7cf9ed15d423fb9bb5883835c1951d36e8804da9ab2ed13d603ed349f12e7cd15885f8279d90d13511b languageName: node linkType: hard -"@mui/system@npm:5.15.5": +"@mui/system@npm:5.15.5, @mui/system@npm:^5.15.5": version: 5.15.5 resolution: "@mui/system@npm:5.15.5" dependencies: @@ -3228,7 +3288,7 @@ __metadata: languageName: node linkType: hard -"@mui/types@npm:^7.2.11, @mui/types@npm:^7.2.4, @mui/types@npm:^7.2.8": +"@mui/types@npm:^7.2.11, @mui/types@npm:^7.2.4": version: 7.2.11 resolution: "@mui/types@npm:7.2.11" peerDependencies: @@ -6074,7 +6134,7 @@ __metadata: languageName: node linkType: hard -"@types/react-transition-group@npm:^4.4.6": +"@types/react-transition-group@npm:^4.4.10, @types/react-transition-group@npm:^4.4.6": version: 4.4.10 resolution: "@types/react-transition-group@npm:4.4.10" dependencies: @@ -17558,8 +17618,8 @@ __metadata: "@material/material-color-utilities": 0.2.7 "@mui/icons-material": 5.14.9 "@mui/lab": 5.0.0-alpha.145 - "@mui/material": 5.14.10 - "@mui/styles": 5.14.16 + "@mui/material": 5.15.5 + "@mui/styles": 5.15.5 "@mui/system": 5.15.5 "@mui/x-data-grid": 6.18.7 "@playwright/test": 1.40.1 From 07d7b8b1c4ca711d4e1cac56779b70cf2070a688 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 24 Jan 2024 12:23:46 -0600 Subject: [PATCH 17/18] statusmgr: expire status subscriptions after 7 days (#3610) * delete status subscriptions after 7 days * set timestamp on update * add smoketest * update schema --- engine/statusmgr/db.go | 2 +- engine/statusmgr/queries.sql | 15 ++-- engine/statusmgr/update.go | 5 ++ gadb/models.go | 1 + gadb/queries.sql.go | 21 +++-- ...0240118120349-status-update-expiration.sql | 27 ++++++ migrate/schema.sql | 8 +- test/smoke/statusupdatesexpire_test.go | 84 +++++++++++++++++++ 8 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 migrate/migrations/20240118120349-status-update-expiration.sql create mode 100644 test/smoke/statusupdatesexpire_test.go diff --git a/engine/statusmgr/db.go b/engine/statusmgr/db.go index 17d307f437..a2e5aacacf 100644 --- a/engine/statusmgr/db.go +++ b/engine/statusmgr/db.go @@ -19,7 +19,7 @@ func (db *DB) Name() string { return "Engine.StatusUpdateManager" } func NewDB(ctx context.Context, db *sql.DB) (*DB, error) { lock, err := processinglock.NewLock(ctx, db, processinglock.Config{ Type: processinglock.TypeStatusUpdate, - Version: 4, + Version: 5, }) if err != nil { return nil, err diff --git a/engine/statusmgr/queries.sql b/engine/statusmgr/queries.sql index ffdb90bc70..ac319d9d6e 100644 --- a/engine/statusmgr/queries.sql +++ b/engine/statusmgr/queries.sql @@ -19,7 +19,7 @@ SELECT channel_id, contact_method_id, alert_id, - ( +( SELECT status FROM @@ -29,7 +29,7 @@ SELECT FROM alert_status_subscriptions sub WHERE - sub.last_alert_status != ( + sub.last_alert_status !=( SELECT status FROM @@ -70,18 +70,23 @@ DELETE FROM alert_status_subscriptions WHERE id = $1; -- name: StatusMgrSendUserMsg :exec -INSERT INTO outgoing_messages (id, message_type, contact_method_id, user_id, alert_id, alert_log_id) +INSERT INTO outgoing_messages(id, message_type, contact_method_id, user_id, alert_id, alert_log_id) VALUES (@id::uuid, 'alert_status_update', @cm_id::uuid, @user_id::uuid, @alert_id::bigint, @log_id); -- name: StatusMgrSendChannelMsg :exec -INSERT INTO outgoing_messages (id, message_type, channel_id, alert_id, alert_log_id) +INSERT INTO outgoing_messages(id, message_type, channel_id, alert_id, alert_log_id) VALUES (@id::uuid, 'alert_status_update', @channel_id::uuid, @alert_id::bigint, @log_id); -- name: StatusMgrUpdateSub :exec UPDATE alert_status_subscriptions SET - last_alert_status = $2 + last_alert_status = $2, + updated_at = now() WHERE id = $1; +-- name: StatusMgrCleanupStaleSubs :exec +DELETE FROM alert_status_subscriptions sub +WHERE sub.updated_at < now() - '7 days'::interval; + diff --git a/engine/statusmgr/update.go b/engine/statusmgr/update.go index 9981077262..fb452fa2f5 100644 --- a/engine/statusmgr/update.go +++ b/engine/statusmgr/update.go @@ -35,6 +35,11 @@ func (db *DB) UpdateAll(ctx context.Context) error { return fmt.Errorf("delete status subscriptions for disabled contact methods: %w", err) } + err = q.StatusMgrCleanupStaleSubs(ctx) + if err != nil { + return fmt.Errorf("delete stale status subscriptions: %w", err) + } + return nil }) if err != nil { diff --git a/gadb/models.go b/gadb/models.go index 518331f23a..079fb736f9 100644 --- a/gadb/models.go +++ b/gadb/models.go @@ -798,6 +798,7 @@ type AlertStatusSubscription struct { ContactMethodID uuid.NullUUID ID int64 LastAlertStatus EnumAlertStatus + UpdatedAt time.Time } type AuthBasicUser struct { diff --git a/gadb/queries.sql.go b/gadb/queries.sql.go index 3cd60173a1..4725e8846a 100644 --- a/gadb/queries.sql.go +++ b/gadb/queries.sql.go @@ -1897,6 +1897,16 @@ func (q *Queries) StatusMgrCleanupDisabledSubs(ctx context.Context) error { return err } +const statusMgrCleanupStaleSubs = `-- name: StatusMgrCleanupStaleSubs :exec +DELETE FROM alert_status_subscriptions sub +WHERE sub.updated_at < now() - '7 days'::interval +` + +func (q *Queries) StatusMgrCleanupStaleSubs(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, statusMgrCleanupStaleSubs) + return err +} + const statusMgrDeleteSub = `-- name: StatusMgrDeleteSub :exec DELETE FROM alert_status_subscriptions WHERE id = $1 @@ -1945,7 +1955,7 @@ SELECT channel_id, contact_method_id, alert_id, - ( +( SELECT status FROM @@ -1955,7 +1965,7 @@ SELECT FROM alert_status_subscriptions sub WHERE - sub.last_alert_status != ( + sub.last_alert_status !=( SELECT status FROM @@ -1989,7 +1999,7 @@ func (q *Queries) StatusMgrNextUpdate(ctx context.Context) (StatusMgrNextUpdateR } const statusMgrSendChannelMsg = `-- name: StatusMgrSendChannelMsg :exec -INSERT INTO outgoing_messages (id, message_type, channel_id, alert_id, alert_log_id) +INSERT INTO outgoing_messages(id, message_type, channel_id, alert_id, alert_log_id) VALUES ($1::uuid, 'alert_status_update', $2::uuid, $3::bigint, $4) ` @@ -2011,7 +2021,7 @@ func (q *Queries) StatusMgrSendChannelMsg(ctx context.Context, arg StatusMgrSend } const statusMgrSendUserMsg = `-- name: StatusMgrSendUserMsg :exec -INSERT INTO outgoing_messages (id, message_type, contact_method_id, user_id, alert_id, alert_log_id) +INSERT INTO outgoing_messages(id, message_type, contact_method_id, user_id, alert_id, alert_log_id) VALUES ($1::uuid, 'alert_status_update', $2::uuid, $3::uuid, $4::bigint, $5) ` @@ -2053,7 +2063,8 @@ const statusMgrUpdateSub = `-- name: StatusMgrUpdateSub :exec UPDATE alert_status_subscriptions SET - last_alert_status = $2 + last_alert_status = $2, + updated_at = now() WHERE id = $1 ` diff --git a/migrate/migrations/20240118120349-status-update-expiration.sql b/migrate/migrations/20240118120349-status-update-expiration.sql new file mode 100644 index 0000000000..b002bcc87b --- /dev/null +++ b/migrate/migrations/20240118120349-status-update-expiration.sql @@ -0,0 +1,27 @@ +-- +migrate Up +-- increment module version +UPDATE + engine_processing_versions +SET + version = 5 +WHERE + type_id = 'status_update'; + +ALTER TABLE alert_status_subscriptions + ADD COLUMN updated_at timestamptz NOT NULL DEFAULT now(); + +CREATE INDEX alert_status_subscriptions_updated_at_idx ON alert_status_subscriptions(updated_at); + +-- +migrate Down +UPDATE + engine_processing_versions +SET + version = 4 +WHERE + type_id = 'status_update'; + +DROP INDEX alert_status_subscriptions_updated_at_idx; + +ALTER TABLE alert_status_subscriptions + DROP COLUMN updated_at; + diff --git a/migrate/schema.sql b/migrate/schema.sql index cacddd22b7..de8b19892f 100644 --- a/migrate/schema.sql +++ b/migrate/schema.sql @@ -1,7 +1,7 @@ -- This file is auto-generated by "make db-schema"; DO NOT EDIT --- DATA=5064b6e0d3f391bf44fee4dfa88da2b83e03cbf7ead5b9380529ce9c0d2c3460 - --- DISK=ad8fee2ce10b4f87e0ea8620202aa04b07c5a53774089ced9c3cb6c880747963 - --- PSQL=ad8fee2ce10b4f87e0ea8620202aa04b07c5a53774089ced9c3cb6c880747963 - +-- DATA=7ad22b14baea5296b0f2a3081981fda35134ecb9a0061f1a414cc50229615125 - +-- DISK=a58f8d1789d08de54def62127f47153bc5005a0a8a92985d23635e3973d3537a - +-- PSQL=a58f8d1789d08de54def62127f47153bc5005a0a8a92985d23635e3973d3537a - -- -- pgdump-lite database dump -- @@ -1317,6 +1317,7 @@ CREATE TABLE alert_status_subscriptions ( contact_method_id uuid, id bigint DEFAULT nextval('alert_status_subscriptions_id_seq'::regclass) NOT NULL, last_alert_status enum_alert_status NOT NULL, + updated_at timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT alert_status_subscriptions_alert_id_fkey FOREIGN KEY (alert_id) REFERENCES alerts(id) ON DELETE CASCADE, CONSTRAINT alert_status_subscriptions_channel_id_contact_method_id_ale_key UNIQUE (channel_id, contact_method_id, alert_id), CONSTRAINT alert_status_subscriptions_channel_id_fkey FOREIGN KEY (channel_id) REFERENCES notification_channels(id) ON DELETE CASCADE, @@ -1327,6 +1328,7 @@ CREATE TABLE alert_status_subscriptions ( CREATE UNIQUE INDEX alert_status_subscriptions_channel_id_contact_method_id_ale_key ON public.alert_status_subscriptions USING btree (channel_id, contact_method_id, alert_id); CREATE UNIQUE INDEX alert_status_subscriptions_pkey ON public.alert_status_subscriptions USING btree (id); +CREATE INDEX alert_status_subscriptions_updated_at_idx ON public.alert_status_subscriptions USING btree (updated_at); CREATE TABLE alerts ( diff --git a/test/smoke/statusupdatesexpire_test.go b/test/smoke/statusupdatesexpire_test.go new file mode 100644 index 0000000000..2477dc2575 --- /dev/null +++ b/test/smoke/statusupdatesexpire_test.go @@ -0,0 +1,84 @@ +package smoke + +import ( + "bytes" + "net/http" + "net/url" + "testing" + "time" + + "github.com/target/goalert/test/smoke/harness" +) + +// TestStatusUpdatesExpiration checks expiration functionality of status updates: +// +// - status updates should not be sent after 7 days of inactivity +func TestStatusUpdatesExpiration(t *testing.T) { + t.Parallel() + + sql := ` + insert into users (id, name, email, role) + values + ({{uuid "user"}}, 'bob', 'joe@test.com', 'admin'); + insert into user_contact_methods (id, user_id, name, type, value, pending, enable_status_updates) + values + ({{uuid "cm1"}}, {{uuid "user"}}, 'personal', 'SMS', {{phone "1"}}, false, true); + + insert into user_notification_rules (user_id, contact_method_id, delay_minutes) + values + ({{uuid "user"}}, {{uuid "cm1"}}, 0); + + insert into escalation_policies (id, name) + values + ({{uuid "eid"}}, 'esc policy'); + insert into escalation_policy_steps (id, escalation_policy_id) + values + ({{uuid "esid"}}, {{uuid "eid"}}); + insert into escalation_policy_actions (escalation_policy_step_id, user_id) + values + ({{uuid "esid"}}, {{uuid "user"}}); + + insert into services (id, escalation_policy_id, name) + values + ({{uuid "sid"}}, {{uuid "eid"}}, 'service'); + + insert into integration_keys (id, service_id, type, name) + values + ({{uuid "int1"}}, {{uuid "sid"}}, 'generic', 'test'); + + insert into alerts (service_id, source, summary, dedup_key) + values + ({{uuid "sid"}}, 'manual', 'first alert', 'user:1:first'), + ({{uuid "sid"}}, 'manual', 'second alert', 'user:1:second'); + +` + h := harness.NewHarness(t, sql, "status-update-expiration") + defer h.Close() + + doClose := func(dedup string) { + u := h.URL() + "/v1/api/alerts?key=" + h.UUID("int1") + v := make(url.Values) + v.Set("dedup", dedup) + v.Set("action", "close") + resp, err := http.Post(u, "application/x-www-form-urlencoded", bytes.NewBufferString(v.Encode())) + if err != nil { + t.Fatal("post to generic endpoint failed:", err) + } else if resp.StatusCode/100 != 2 { + t.Error("non-2xx response:", resp.Status) + } + resp.Body.Close() + } + + tw := h.Twilio(t) + d1 := tw.Device(h.Phone("1")) + + d1.ExpectSMS("first alert") + d1.ExpectSMS("second alert") + + doClose("first") + d1.ExpectSMS("closed") + + h.FastForward(7 * 24 * time.Hour) + + doClose("second") +} From 13acd084d0c87051960c430a9ecb54836a670fa5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:54:18 -0600 Subject: [PATCH 18/18] build(deps-dev): bump @mui/lab from 5.0.0-alpha.145 to 5.0.0-alpha.162 (#3624) Bumps [@mui/lab](https://github.com/mui/material-ui/tree/HEAD/packages/mui-lab) from 5.0.0-alpha.145 to 5.0.0-alpha.162. - [Release notes](https://github.com/mui/material-ui/releases) - [Changelog](https://github.com/mui/material-ui/blob/master/CHANGELOG.md) - [Commits](https://github.com/mui/material-ui/commits/HEAD/packages/mui-lab) --- updated-dependencies: - dependency-name: "@mui/lab" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 177 +++++++++++++++++++++++---------------------------- 2 files changed, 81 insertions(+), 98 deletions(-) diff --git a/package.json b/package.json index e62dc3ec24..bfbeafc3ab 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@emotion/styled": "11.11.0", "@material/material-color-utilities": "0.2.7", "@mui/icons-material": "5.14.9", - "@mui/lab": "5.0.0-alpha.145", + "@mui/lab": "5.0.0-alpha.162", "@mui/material": "5.15.5", "@mui/styles": "5.15.5", "@mui/system": "5.15.5", diff --git a/yarn.lock b/yarn.lock index 9722e46404..51e9f88a61 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1513,7 +1513,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.22.15, @babel/runtime@npm:^7.22.6, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.22.15, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.6, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.23.6 resolution: "@babel/runtime@npm:7.23.6" dependencies: @@ -2349,7 +2349,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.0.0, @floating-ui/react-dom@npm:^2.0.2": +"@floating-ui/react-dom@npm:^2.0.0": version: 2.0.4 resolution: "@floating-ui/react-dom@npm:2.0.4" dependencies: @@ -2361,7 +2361,7 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react-dom@npm:^2.0.5": +"@floating-ui/react-dom@npm:^2.0.5, @floating-ui/react-dom@npm:^2.0.6": version: 2.0.6 resolution: "@floating-ui/react-dom@npm:2.0.6" dependencies: @@ -2995,16 +2995,16 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-beta.16": - version: 5.0.0-beta.16 - resolution: "@mui/base@npm:5.0.0-beta.16" +"@mui/base@npm:5.0.0-beta.32": + version: 5.0.0-beta.32 + resolution: "@mui/base@npm:5.0.0-beta.32" dependencies: - "@babel/runtime": ^7.22.15 - "@floating-ui/react-dom": ^2.0.2 - "@mui/types": ^7.2.4 - "@mui/utils": ^5.14.10 + "@babel/runtime": ^7.23.8 + "@floating-ui/react-dom": ^2.0.5 + "@mui/types": ^7.2.13 + "@mui/utils": ^5.15.5 "@popperjs/core": ^2.11.8 - clsx: ^2.0.0 + clsx: ^2.1.0 prop-types: ^15.8.1 peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 @@ -3013,18 +3013,18 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: b21589888c62d406feaae4f8f903a70cc847d1c64c812cab0bbb37662bda6e6256e3a8a75957f9133e743f7ff8c4770c263579cfc80241c2c34b2bc6abb819da + checksum: 5f27be8914c072ffcbe6720de9aa6129180e68927657e8bcbc03a6f322d1ee6c6740a199d72ed0b490a7b29b79cc0c59d1e05a427089b17f4cbc9cc756e67506 languageName: node linkType: hard -"@mui/base@npm:5.0.0-beta.32": - version: 5.0.0-beta.32 - resolution: "@mui/base@npm:5.0.0-beta.32" +"@mui/base@npm:5.0.0-beta.33": + version: 5.0.0-beta.33 + resolution: "@mui/base@npm:5.0.0-beta.33" dependencies: "@babel/runtime": ^7.23.8 - "@floating-ui/react-dom": ^2.0.5 + "@floating-ui/react-dom": ^2.0.6 "@mui/types": ^7.2.13 - "@mui/utils": ^5.15.5 + "@mui/utils": ^5.15.6 "@popperjs/core": ^2.11.8 clsx: ^2.1.0 prop-types: ^15.8.1 @@ -3035,7 +3035,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 5f27be8914c072ffcbe6720de9aa6129180e68927657e8bcbc03a6f322d1ee6c6740a199d72ed0b490a7b29b79cc0c59d1e05a427089b17f4cbc9cc756e67506 + checksum: 5724b2ad6971254944cada34ed06e7bda90c719d01ff5491af71f58e9a1cb6d804dda03bfc16fe5220f817acea18b178ef6b16475e99551ab89348e1e3057bd2 languageName: node linkType: hard @@ -3062,22 +3062,21 @@ __metadata: languageName: node linkType: hard -"@mui/lab@npm:5.0.0-alpha.145": - version: 5.0.0-alpha.145 - resolution: "@mui/lab@npm:5.0.0-alpha.145" +"@mui/lab@npm:5.0.0-alpha.162": + version: 5.0.0-alpha.162 + resolution: "@mui/lab@npm:5.0.0-alpha.162" dependencies: - "@babel/runtime": ^7.22.15 - "@mui/base": 5.0.0-beta.16 - "@mui/system": ^5.14.10 - "@mui/types": ^7.2.4 - "@mui/utils": ^5.14.10 - "@mui/x-tree-view": 6.0.0-alpha.1 - clsx: ^2.0.0 + "@babel/runtime": ^7.23.8 + "@mui/base": 5.0.0-beta.33 + "@mui/system": ^5.15.6 + "@mui/types": ^7.2.13 + "@mui/utils": ^5.15.6 + clsx: ^2.1.0 prop-types: ^15.8.1 peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@mui/material": ^5.0.0 + "@mui/material": ">=5.15.0" "@types/react": ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 @@ -3088,7 +3087,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 3962250b807a07a74d8d2bff97cef7b2ed32168fe7d6a9f2472fe5f5b856c8871be8b9f4c95f4c0ad48e5dbc0416e7baa29babf91d0ad50899e4e198d0814aab + checksum: eff70cb8916949f46eebcd7554d9e9bc649b73224e511e02afec43fe917b61219027cbea350726915ec5e6c3916da891e8840b5e6308255ff8b8c34c63f2cd5c languageName: node linkType: hard @@ -3125,12 +3124,12 @@ __metadata: languageName: node linkType: hard -"@mui/private-theming@npm:^5.15.2": - version: 5.15.2 - resolution: "@mui/private-theming@npm:5.15.2" +"@mui/private-theming@npm:^5.15.5": + version: 5.15.5 + resolution: "@mui/private-theming@npm:5.15.5" dependencies: - "@babel/runtime": ^7.23.6 - "@mui/utils": ^5.15.2 + "@babel/runtime": ^7.23.8 + "@mui/utils": ^5.15.5 prop-types: ^15.8.1 peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 @@ -3138,16 +3137,16 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 63cc69adc3eca03533dd72296aac6449abe133522f6f5c62c8497e57dbd092c131cba3488ee8d97dfb76c31a194df014f4da3b2d30027614e987ed72581a4acf + checksum: 3a5f7f190aa69a0ad69a34f77e54ee2fdcb088d21a61d4720b76d098d178e1c3f6a470fc5e7a30d351db10efbc062cb11af75b8789e2f38eaa2ca612bcb0f851 languageName: node linkType: hard -"@mui/private-theming@npm:^5.15.5": - version: 5.15.5 - resolution: "@mui/private-theming@npm:5.15.5" +"@mui/private-theming@npm:^5.15.6": + version: 5.15.6 + resolution: "@mui/private-theming@npm:5.15.6" dependencies: "@babel/runtime": ^7.23.8 - "@mui/utils": ^5.15.5 + "@mui/utils": ^5.15.6 prop-types: ^15.8.1 peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 @@ -3155,15 +3154,15 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 3a5f7f190aa69a0ad69a34f77e54ee2fdcb088d21a61d4720b76d098d178e1c3f6a470fc5e7a30d351db10efbc062cb11af75b8789e2f38eaa2ca612bcb0f851 + checksum: f76f85fb2f55806518a3cff66c3d1fadab471ff34f8321d3cf0917a2b92933864a86974d13e2714568c72a0faea89ddfeb80e92a29263dd59f8da88f587ee467 languageName: node linkType: hard -"@mui/styled-engine@npm:^5.15.2": - version: 5.15.2 - resolution: "@mui/styled-engine@npm:5.15.2" +"@mui/styled-engine@npm:^5.15.5": + version: 5.15.5 + resolution: "@mui/styled-engine@npm:5.15.5" dependencies: - "@babel/runtime": ^7.23.6 + "@babel/runtime": ^7.23.8 "@emotion/cache": ^11.11.0 csstype: ^3.1.2 prop-types: ^15.8.1 @@ -3176,13 +3175,13 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: daa15b61be2e785c9719e091047a5900aa5aa0b6a9dfbc2ea373f6718f6ec3485f44e05596cd9506a90a7c55d8d372233a80cbeca4d81ceb8d5e6a10ce0f8292 + checksum: 42bb7f50ed33ec88f799bd90a00337689837ee0b2d603681fe69c9a14850e5ae1d037505365ce78cd564307cc2c0654c46cece8d9250adb21c77b1de316e3c45 languageName: node linkType: hard -"@mui/styled-engine@npm:^5.15.5": - version: 5.15.5 - resolution: "@mui/styled-engine@npm:5.15.5" +"@mui/styled-engine@npm:^5.15.6": + version: 5.15.6 + resolution: "@mui/styled-engine@npm:5.15.6" dependencies: "@babel/runtime": ^7.23.8 "@emotion/cache": ^11.11.0 @@ -3197,7 +3196,7 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 42bb7f50ed33ec88f799bd90a00337689837ee0b2d603681fe69c9a14850e5ae1d037505365ce78cd564307cc2c0654c46cece8d9250adb21c77b1de316e3c45 + checksum: ebc0bd5937e1d0a87c33f9c8f435d71ee473771e353215143cfcbcd9dc2c491bc89b98dd9c147a5d53556e169af02cbc7c8513769df856098ac115422910e67f languageName: node linkType: hard @@ -3260,16 +3259,16 @@ __metadata: languageName: node linkType: hard -"@mui/system@npm:^5.14.10": - version: 5.15.2 - resolution: "@mui/system@npm:5.15.2" +"@mui/system@npm:^5.15.6": + version: 5.15.6 + resolution: "@mui/system@npm:5.15.6" dependencies: - "@babel/runtime": ^7.23.6 - "@mui/private-theming": ^5.15.2 - "@mui/styled-engine": ^5.15.2 - "@mui/types": ^7.2.11 - "@mui/utils": ^5.15.2 - clsx: ^2.0.0 + "@babel/runtime": ^7.23.8 + "@mui/private-theming": ^5.15.6 + "@mui/styled-engine": ^5.15.6 + "@mui/types": ^7.2.13 + "@mui/utils": ^5.15.6 + clsx: ^2.1.0 csstype: ^3.1.2 prop-types: ^15.8.1 peerDependencies: @@ -3284,19 +3283,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: ce6297a4cfa6f91891fa84679e05e40e12c4a27fe4fb34d78cbcbae172da293c05fa6f07c24fec66c864eac64179aeec1f7a70358e376b8c16ffc8e91f93a904 - languageName: node - linkType: hard - -"@mui/types@npm:^7.2.11, @mui/types@npm:^7.2.4": - version: 7.2.11 - resolution: "@mui/types@npm:7.2.11" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: ce6bbe8ba963af218bf86797f4c8adbf0f294047adeaf6596d3bb4a96f1b01701f1b1ae7dcfe2f7972f6e23c85eee4187e496fb481541713ed8bf12e96f3c34f + checksum: c987a75ae3d2cf90895347bebeeff87fecc3c13d2bfde39cddd96cb41a0f4614b3b2c3e489857d75a327c35f5af9de49d6dd15b30c131b53e120c3c58df3b79b languageName: node linkType: hard @@ -3312,7 +3299,7 @@ __metadata: languageName: node linkType: hard -"@mui/utils@npm:^5.14.10, @mui/utils@npm:^5.14.16, @mui/utils@npm:^5.14.3, @mui/utils@npm:^5.15.2": +"@mui/utils@npm:^5.14.16": version: 5.15.2 resolution: "@mui/utils@npm:5.15.2" dependencies: @@ -3348,6 +3335,24 @@ __metadata: languageName: node linkType: hard +"@mui/utils@npm:^5.15.6": + version: 5.15.6 + resolution: "@mui/utils@npm:5.15.6" + dependencies: + "@babel/runtime": ^7.23.8 + "@types/prop-types": ^15.7.11 + prop-types: ^15.8.1 + react-is: ^18.2.0 + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: c5675fb3e4c6c887c00a81e6596a7c827059c8e7b02a92ee65cfc899c51529762087cf283e0dcfec7824f6decce15f6dbe5775d847f2aff1eda7fb722cd1b705 + languageName: node + linkType: hard + "@mui/x-data-grid@npm:6.18.7": version: 6.18.7 resolution: "@mui/x-data-grid@npm:6.18.7" @@ -3366,28 +3371,6 @@ __metadata: languageName: node linkType: hard -"@mui/x-tree-view@npm:6.0.0-alpha.1": - version: 6.0.0-alpha.1 - resolution: "@mui/x-tree-view@npm:6.0.0-alpha.1" - dependencies: - "@babel/runtime": ^7.22.6 - "@mui/utils": ^5.14.3 - "@types/react-transition-group": ^4.4.6 - clsx: ^2.0.0 - prop-types: ^15.8.1 - react-transition-group: ^4.4.5 - peerDependencies: - "@emotion/react": ^11.9.0 - "@emotion/styled": ^11.8.1 - "@mui/base": ^5.0.0-alpha.87 - "@mui/material": ^5.8.6 - "@mui/system": ^5.8.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - checksum: 87e659401c81ba4b1ed7eccb9e3167b4d22f27630c26bb436ff6eb4f79aa2fc0f3c50afda4625c9df898bcd1710c4de47813ee8f5bba51d0d9c85fb678a53658 - languageName: node - linkType: hard - "@n1ru4l/push-pull-async-iterable-iterator@npm:^3.1.0": version: 3.2.0 resolution: "@n1ru4l/push-pull-async-iterable-iterator@npm:3.2.0" @@ -6134,7 +6117,7 @@ __metadata: languageName: node linkType: hard -"@types/react-transition-group@npm:^4.4.10, @types/react-transition-group@npm:^4.4.6": +"@types/react-transition-group@npm:^4.4.10": version: 4.4.10 resolution: "@types/react-transition-group@npm:4.4.10" dependencies: @@ -17617,7 +17600,7 @@ __metadata: "@emotion/styled": 11.11.0 "@material/material-color-utilities": 0.2.7 "@mui/icons-material": 5.14.9 - "@mui/lab": 5.0.0-alpha.145 + "@mui/lab": 5.0.0-alpha.162 "@mui/material": 5.15.5 "@mui/styles": 5.15.5 "@mui/system": 5.15.5