From e81adc3183214b65faf43ba075104f347cdf4301 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 6 Nov 2023 15:30:00 -0600 Subject: [PATCH 01/11] add columns for new label types --- ...1106152429-schedule-rotation-ep-labels.sql | 21 +++++++++++++++++++ migrate/schema.sql | 18 +++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 migrate/migrations/20231106152429-schedule-rotation-ep-labels.sql diff --git a/migrate/migrations/20231106152429-schedule-rotation-ep-labels.sql b/migrate/migrations/20231106152429-schedule-rotation-ep-labels.sql new file mode 100644 index 0000000000..965553fb19 --- /dev/null +++ b/migrate/migrations/20231106152429-schedule-rotation-ep-labels.sql @@ -0,0 +1,21 @@ +-- +migrate Up +ALTER TABLE labels + ADD COLUMN tgt_schedule_id uuid REFERENCES schedules(id) ON DELETE CASCADE, + ADD COLUMN tgt_rotation_id uuid REFERENCES rotations(id) ON DELETE CASCADE, + ADD COLUMN tgt_ep_id uuid REFERENCES escalation_policies(id) ON DELETE CASCADE, + DROP CONSTRAINT labels_tgt_service_id_key_key; + +-- drop redundant index +DROP INDEX idx_labels_service_id; + +CREATE UNIQUE INDEX idx_labels_resource_id ON labels(tgt_service_id, tgt_schedule_id, tgt_rotation_id, tgt_ep_id, key); + +-- +migrate Down +ALTER TABLE labels + DROP COLUMN tgt_schedule_id, + DROP COLUMN tgt_rotation_id, + DROP COLUMN tgt_ep_id, + ADD CONSTRAINT labels_tgt_service_id_key_key UNIQUE (tgt_service_id, key); + +CREATE INDEX idx_labels_service_id ON labels(tgt_service_id); + diff --git a/migrate/schema.sql b/migrate/schema.sql index cacddd22b7..35b2f5d422 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=256a801de96f5b86d70f023e7324db7027f6e0f74ab8bc747a408794441e63b6 - +-- DISK=11c6733e21df09221d04b58a363c2687b47269bd2b8092be6b2a8120d8e366b7 - +-- PSQL=11c6733e21df09221d04b58a363c2687b47269bd2b8092be6b2a8120d8e366b7 - -- -- pgdump-lite database dump -- @@ -1692,16 +1692,20 @@ CREATE UNIQUE INDEX keyring_pkey ON public.keyring USING btree (id); CREATE TABLE labels ( id bigint DEFAULT nextval('labels_id_seq'::regclass) NOT NULL, key text NOT NULL, + tgt_ep_id uuid, + tgt_rotation_id uuid, + tgt_schedule_id uuid, tgt_service_id uuid NOT NULL, value text NOT NULL, CONSTRAINT labels_pkey PRIMARY KEY (id), - CONSTRAINT labels_tgt_service_id_fkey FOREIGN KEY (tgt_service_id) REFERENCES services(id) ON DELETE CASCADE, - CONSTRAINT labels_tgt_service_id_key_key UNIQUE (tgt_service_id, key) + CONSTRAINT labels_tgt_ep_id_fkey FOREIGN KEY (tgt_ep_id) REFERENCES escalation_policies(id) ON DELETE CASCADE, + CONSTRAINT labels_tgt_rotation_id_fkey FOREIGN KEY (tgt_rotation_id) REFERENCES rotations(id) ON DELETE CASCADE, + CONSTRAINT labels_tgt_schedule_id_fkey FOREIGN KEY (tgt_schedule_id) REFERENCES schedules(id) ON DELETE CASCADE, + CONSTRAINT labels_tgt_service_id_fkey FOREIGN KEY (tgt_service_id) REFERENCES services(id) ON DELETE CASCADE ); -CREATE INDEX idx_labels_service_id ON public.labels USING btree (tgt_service_id); +CREATE UNIQUE INDEX idx_labels_resource_id ON public.labels USING btree (tgt_service_id, tgt_schedule_id, tgt_rotation_id, tgt_ep_id, key); CREATE UNIQUE INDEX labels_pkey ON public.labels USING btree (id); -CREATE UNIQUE INDEX labels_tgt_service_id_key_key ON public.labels USING btree (tgt_service_id, key); CREATE TABLE notification_channels ( From aa59b6bb2994063957e65b4d302d129cc31b8732 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 6 Nov 2023 16:05:36 -0600 Subject: [PATCH 02/11] add new types to queries --- label/queries.sql | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/label/queries.sql b/label/queries.sql index 5ac77366e4..97adb9e6f2 100644 --- a/label/queries.sql +++ b/label/queries.sql @@ -11,17 +11,23 @@ SELECT FROM labels WHERE - tgt_service_id = $1; + tgt_service_id = sqlc.narg(service_id)::uuid + OR tgt_schedule_id = sqlc.narg(schedule_id)::uuid + OR tgt_rotation_id = sqlc.narg(rotation_id)::uuid + OR tgt_ep_id = sqlc.narg(ep_id)::uuid; -- name: LabelDeleteKeyByTarget :exec DELETE FROM labels WHERE key = $1 - AND tgt_service_id = $2; + AND (tgt_service_id = sqlc.narg(service_id)::uuid, + OR tgt_schedule_id = sqlc.narg(schedule_id)::uuid, + OR tgt_rotation_id = sqlc.narg(rotation_id)::uuid, + OR tgt_ep_id = sqlc.narg(ep_id)::uuid); -- name: LabelSetByTarget :exec -INSERT INTO labels(key, value, tgt_service_id) - VALUES ($1, $2, $3) -ON CONFLICT (key, tgt_service_id) +INSERT INTO labels(key, value, tgt_service_id, tgt_schedule_id, tgt_rotation_id, tgt_ep_id) + VALUES ($1, $2, sqlc.narg(service_id)::uuid, sqlc.narg(schedule_id)::uuid, sqlc.narg(rotation_id)::uuid, sqlc.narg(ep_id)::uuid) +ON CONFLICT (key, tgt_service_id, tgt_schedule_id, tgt_rotation_id, tgt_ep_id) DO UPDATE SET value = $2; From ee7a7ca63ab27cfed91bbda673a49a344f3dae52 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 6 Nov 2023 16:50:24 -0600 Subject: [PATCH 03/11] update label store for all target types --- gadb/models.go | 11 +++++--- gadb/queries.sql.go | 65 +++++++++++++++++++++++++++++++++--------- label/queries.sql | 6 ++-- label/store.go | 69 ++++++++++++++++++++++++++++++++++++--------- 4 files changed, 117 insertions(+), 34 deletions(-) diff --git a/gadb/models.go b/gadb/models.go index 518331f23a..801a3b7912 100644 --- a/gadb/models.go +++ b/gadb/models.go @@ -953,10 +953,13 @@ type Keyring struct { } type Label struct { - ID int64 - Key string - TgtServiceID uuid.UUID - Value string + ID int64 + Key string + TgtEpID uuid.NullUUID + TgtRotationID uuid.NullUUID + TgtScheduleID uuid.NullUUID + TgtServiceID uuid.UUID + Value string } type NotificationChannel struct { diff --git a/gadb/queries.sql.go b/gadb/queries.sql.go index 3cd60173a1..cc835b9c9d 100644 --- a/gadb/queries.sql.go +++ b/gadb/queries.sql.go @@ -1444,16 +1444,28 @@ func (q *Queries) IntKeyGetServiceID(ctx context.Context, arg IntKeyGetServiceID const labelDeleteKeyByTarget = `-- name: LabelDeleteKeyByTarget :exec DELETE FROM labels WHERE key = $1 - AND tgt_service_id = $2 + AND (tgt_service_id = $2::uuid + OR tgt_schedule_id = $3::uuid + OR tgt_rotation_id = $4::uuid + OR tgt_ep_id = $5::uuid) ` type LabelDeleteKeyByTargetParams struct { - Key string - TgtServiceID uuid.UUID + Key string + ServiceID uuid.NullUUID + ScheduleID uuid.NullUUID + RotationID uuid.NullUUID + EpID uuid.NullUUID } func (q *Queries) LabelDeleteKeyByTarget(ctx context.Context, arg LabelDeleteKeyByTargetParams) error { - _, err := q.db.ExecContext(ctx, labelDeleteKeyByTarget, arg.Key, arg.TgtServiceID) + _, err := q.db.ExecContext(ctx, labelDeleteKeyByTarget, + arg.Key, + arg.ServiceID, + arg.ScheduleID, + arg.RotationID, + arg.EpID, + ) return err } @@ -1464,16 +1476,31 @@ SELECT FROM labels WHERE - tgt_service_id = $1 + tgt_service_id = $1::uuid + OR tgt_schedule_id = $2::uuid + OR tgt_rotation_id = $3::uuid + OR tgt_ep_id = $4::uuid ` +type LabelFindAllByTargetParams struct { + ServiceID uuid.NullUUID + ScheduleID uuid.NullUUID + RotationID uuid.NullUUID + EpID uuid.NullUUID +} + type LabelFindAllByTargetRow struct { Key string Value string } -func (q *Queries) LabelFindAllByTarget(ctx context.Context, tgtServiceID uuid.UUID) ([]LabelFindAllByTargetRow, error) { - rows, err := q.db.QueryContext(ctx, labelFindAllByTarget, tgtServiceID) +func (q *Queries) LabelFindAllByTarget(ctx context.Context, arg LabelFindAllByTargetParams) ([]LabelFindAllByTargetRow, error) { + rows, err := q.db.QueryContext(ctx, labelFindAllByTarget, + arg.ServiceID, + arg.ScheduleID, + arg.RotationID, + arg.EpID, + ) if err != nil { return nil, err } @@ -1496,21 +1523,31 @@ func (q *Queries) LabelFindAllByTarget(ctx context.Context, tgtServiceID uuid.UU } const labelSetByTarget = `-- name: LabelSetByTarget :exec -INSERT INTO labels(key, value, tgt_service_id) - VALUES ($1, $2, $3) -ON CONFLICT (key, tgt_service_id) +INSERT INTO labels(key, value, tgt_service_id, tgt_schedule_id, tgt_rotation_id, tgt_ep_id) + VALUES ($1, $2, $3::uuid, $4::uuid, $5::uuid, $6::uuid) +ON CONFLICT (key, tgt_service_id, tgt_schedule_id, tgt_rotation_id, tgt_ep_id) DO UPDATE SET value = $2 ` type LabelSetByTargetParams struct { - Key string - Value string - TgtServiceID uuid.UUID + Key string + Value string + ServiceID uuid.NullUUID + ScheduleID uuid.NullUUID + RotationID uuid.NullUUID + EpID uuid.NullUUID } func (q *Queries) LabelSetByTarget(ctx context.Context, arg LabelSetByTargetParams) error { - _, err := q.db.ExecContext(ctx, labelSetByTarget, arg.Key, arg.Value, arg.TgtServiceID) + _, err := q.db.ExecContext(ctx, labelSetByTarget, + arg.Key, + arg.Value, + arg.ServiceID, + arg.ScheduleID, + arg.RotationID, + arg.EpID, + ) return err } diff --git a/label/queries.sql b/label/queries.sql index 97adb9e6f2..5da1e63bef 100644 --- a/label/queries.sql +++ b/label/queries.sql @@ -19,9 +19,9 @@ WHERE -- name: LabelDeleteKeyByTarget :exec DELETE FROM labels WHERE key = $1 - AND (tgt_service_id = sqlc.narg(service_id)::uuid, - OR tgt_schedule_id = sqlc.narg(schedule_id)::uuid, - OR tgt_rotation_id = sqlc.narg(rotation_id)::uuid, + AND (tgt_service_id = sqlc.narg(service_id)::uuid + OR tgt_schedule_id = sqlc.narg(schedule_id)::uuid + OR tgt_rotation_id = sqlc.narg(rotation_id)::uuid OR tgt_ep_id = sqlc.narg(ep_id)::uuid); -- name: LabelSetByTarget :exec diff --git a/label/store.go b/label/store.go index 0de87877d6..f9429c563f 100644 --- a/label/store.go +++ b/label/store.go @@ -9,7 +9,6 @@ import ( "github.com/target/goalert/assignment" "github.com/target/goalert/gadb" "github.com/target/goalert/permission" - "github.com/target/goalert/validation/validate" "github.com/pkg/errors" ) @@ -22,6 +21,38 @@ type Store struct { // NewStore will Set a DB backend from a sql.DB. An error will be returned if statements fail to prepare. func NewStore(ctx context.Context, db *sql.DB) (*Store, error) { return &Store{db: db}, nil } +func (l *Label) tgtEP() uuid.NullUUID { + if l.Target.TargetType() != assignment.TargetTypeEscalationPolicy { + return uuid.NullUUID{} + } + + return uuid.NullUUID{UUID: uuid.MustParse(l.Target.TargetID()), Valid: true} +} + +func (l *Label) tgtSvc() uuid.NullUUID { + if l.Target.TargetType() != assignment.TargetTypeService { + return uuid.NullUUID{} + } + + return uuid.NullUUID{UUID: uuid.MustParse(l.Target.TargetID()), Valid: true} +} + +func (l *Label) tgtSched() uuid.NullUUID { + if l.Target.TargetType() != assignment.TargetTypeSchedule { + return uuid.NullUUID{} + } + + return uuid.NullUUID{UUID: uuid.MustParse(l.Target.TargetID()), Valid: true} +} + +func (l *Label) tgtRot() uuid.NullUUID { + if l.Target.TargetType() != assignment.TargetTypeRotation { + return uuid.NullUUID{} + } + + return uuid.NullUUID{UUID: uuid.MustParse(l.Target.TargetID()), Valid: true} +} + // SetTx will set a label for the service. It can be used to set the key-value pair for the label, // delete a label or update the value given the label's key. func (s *Store) SetTx(ctx context.Context, db gadb.DBTX, label *Label) error { @@ -37,8 +68,11 @@ func (s *Store) SetTx(ctx context.Context, db gadb.DBTX, label *Label) error { if n.Value == "" { // delete if value is empty err = gadb.New(db).LabelDeleteKeyByTarget(ctx, gadb.LabelDeleteKeyByTargetParams{ - Key: label.Key, - TgtServiceID: uuid.MustParse(label.Target.TargetID()), + Key: label.Key, + ServiceID: label.tgtSvc(), + ScheduleID: label.tgtSched(), + RotationID: label.tgtRot(), + EpID: label.tgtEP(), }) if err != nil { return fmt.Errorf("delete label: %w", err) @@ -48,9 +82,12 @@ func (s *Store) SetTx(ctx context.Context, db gadb.DBTX, label *Label) error { } err = gadb.New(db).LabelSetByTarget(ctx, gadb.LabelSetByTargetParams{ - Key: label.Key, - Value: label.Value, - TgtServiceID: uuid.MustParse(label.Target.TargetID()), + Key: label.Key, + Value: label.Value, + ServiceID: label.tgtSvc(), + ScheduleID: label.tgtSched(), + RotationID: label.tgtRot(), + EpID: label.tgtEP(), }) if err != nil { return fmt.Errorf("set label: %w", err) @@ -61,17 +98,23 @@ func (s *Store) SetTx(ctx context.Context, db gadb.DBTX, label *Label) error { // FindAllByService finds all labels for a particular service. It returns all key-value pairs. func (s *Store) FindAllByService(ctx context.Context, db gadb.DBTX, serviceID string) ([]Label, error) { - err := permission.LimitCheckAny(ctx, permission.System, permission.User) - if err != nil { - return nil, err - } + return s.FindAllByTarget(ctx, db, assignment.ServiceTarget(serviceID)) +} - svc, err := validate.ParseUUID("ServiceID", serviceID) +// FindAllByTarget finds all labels for a particular target. It returns all key-value pairs. +func (s *Store) FindAllByTarget(ctx context.Context, db gadb.DBTX, t assignment.Target) ([]Label, error) { + err := permission.LimitCheckAny(ctx, permission.System, permission.User) if err != nil { return nil, err } - rows, err := gadb.New(db).LabelFindAllByTarget(ctx, svc) + label := Label{Target: t} + rows, err := gadb.New(db).LabelFindAllByTarget(ctx, gadb.LabelFindAllByTargetParams{ + ServiceID: label.tgtSvc(), + ScheduleID: label.tgtSched(), + RotationID: label.tgtRot(), + EpID: label.tgtEP(), + }) if errors.Is(err, sql.ErrNoRows) { return nil, nil } @@ -83,7 +126,7 @@ func (s *Store) FindAllByService(ctx context.Context, db gadb.DBTX, serviceID st for i, l := range rows { labels[i].Key = l.Key labels[i].Value = l.Value - labels[i].Target = assignment.ServiceTarget(serviceID) + labels[i].Target = t } return labels, nil From 1269f217686052faf8f8aa30332090e55c033ba2 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 6 Nov 2023 16:54:45 -0600 Subject: [PATCH 04/11] add labels field for gql types --- graphql2/generated.go | 311 ++++++++++++++++++++++++ graphql2/graphqlapp/escalationpolicy.go | 5 + graphql2/graphqlapp/rotation.go | 5 + graphql2/graphqlapp/schedule.go | 5 + graphql2/graphqlapp/service.go | 2 +- graphql2/schema.graphql | 6 + label/store.go | 5 - 7 files changed, 333 insertions(+), 6 deletions(-) diff --git a/graphql2/generated.go b/graphql2/generated.go index 7474027496..2245602b5a 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -218,6 +218,7 @@ type ComplexityRoot struct { Description func(childComplexity int) int ID func(childComplexity int) int IsFavorite func(childComplexity int) int + Labels func(childComplexity int) int Name func(childComplexity int) int Notices func(childComplexity int) int Repeat func(childComplexity int) int @@ -458,6 +459,7 @@ type ComplexityRoot struct { Description func(childComplexity int) int ID func(childComplexity int) int IsFavorite func(childComplexity int) int + Labels func(childComplexity int) int Name func(childComplexity int) int NextHandoffTimes func(childComplexity int, num *int) int ShiftLength func(childComplexity int) int @@ -504,6 +506,7 @@ type ComplexityRoot struct { Description func(childComplexity int) int ID func(childComplexity int) int IsFavorite func(childComplexity int) int + Labels func(childComplexity int) int Name func(childComplexity int) int OnCallNotificationRules func(childComplexity int) int Shifts func(childComplexity int, start time.Time, end time.Time, userIDs []string) int @@ -723,6 +726,7 @@ type EscalationPolicyResolver interface { AssignedTo(ctx context.Context, obj *escalation.Policy) ([]assignment.RawTarget, error) Steps(ctx context.Context, obj *escalation.Policy) ([]escalation.Step, error) Notices(ctx context.Context, obj *escalation.Policy) ([]notice.Notice, error) + Labels(ctx context.Context, obj *escalation.Policy) ([]label.Label, error) } type EscalationPolicyStepResolver interface { Targets(ctx context.Context, obj *escalation.Step) ([]assignment.RawTarget, error) @@ -858,6 +862,7 @@ type RotationResolver interface { UserIDs(ctx context.Context, obj *rotation.Rotation) ([]string, error) Users(ctx context.Context, obj *rotation.Rotation) ([]user.User, error) NextHandoffTimes(ctx context.Context, obj *rotation.Rotation, num *int) ([]time.Time, error) + Labels(ctx context.Context, obj *rotation.Rotation) ([]label.Label, error) } type ScheduleResolver interface { TimeZone(ctx context.Context, obj *schedule.Schedule) (string, error) @@ -868,6 +873,7 @@ type ScheduleResolver interface { IsFavorite(ctx context.Context, obj *schedule.Schedule) (bool, error) TemporarySchedules(ctx context.Context, obj *schedule.Schedule) ([]schedule.TemporarySchedule, error) OnCallNotificationRules(ctx context.Context, obj *schedule.Schedule) ([]schedule.OnCallNotificationRule, error) + Labels(ctx context.Context, obj *schedule.Schedule) ([]label.Label, error) } type ScheduleRuleResolver interface { Target(ctx context.Context, obj *rule.Rule) (*assignment.RawTarget, error) @@ -1461,6 +1467,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.EscalationPolicy.IsFavorite(childComplexity), true + case "EscalationPolicy.labels": + if e.complexity.EscalationPolicy.Labels == nil { + break + } + + return e.complexity.EscalationPolicy.Labels(childComplexity), true + case "EscalationPolicy.name": if e.complexity.EscalationPolicy.Name == nil { break @@ -3109,6 +3122,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Rotation.IsFavorite(childComplexity), true + case "Rotation.labels": + if e.complexity.Rotation.Labels == nil { + break + } + + return e.complexity.Rotation.Labels(childComplexity), true + case "Rotation.name": if e.complexity.Rotation.Name == nil { break @@ -3331,6 +3351,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Schedule.IsFavorite(childComplexity), true + case "Schedule.labels": + if e.complexity.Schedule.Labels == nil { + break + } + + return e.complexity.Schedule.Labels(childComplexity), true + case "Schedule.name": if e.complexity.Schedule.Name == nil { break @@ -9333,6 +9360,56 @@ func (ec *executionContext) fieldContext_EscalationPolicy_notices(ctx context.Co return fc, nil } +func (ec *executionContext) _EscalationPolicy_labels(ctx context.Context, field graphql.CollectedField, obj *escalation.Policy) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EscalationPolicy_labels(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.EscalationPolicy().Labels(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]label.Label) + fc.Result = res + return ec.marshalNLabel2ᚕgithubᚗcomᚋtargetᚋgoalertᚋlabelᚐLabelᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EscalationPolicy_labels(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EscalationPolicy", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "key": + return ec.fieldContext_Label_key(ctx, field) + case "value": + return ec.fieldContext_Label_value(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Label", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _EscalationPolicyConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *EscalationPolicyConnection) (ret graphql.Marshaler) { fc, err := ec.fieldContext_EscalationPolicyConnection_nodes(ctx, field) if err != nil { @@ -9388,6 +9465,8 @@ func (ec *executionContext) fieldContext_EscalationPolicyConnection_nodes(ctx co return ec.fieldContext_EscalationPolicy_steps(ctx, field) case "notices": return ec.fieldContext_EscalationPolicy_notices(ctx, field) + case "labels": + return ec.fieldContext_EscalationPolicy_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type EscalationPolicy", field.Name) }, @@ -9681,6 +9760,8 @@ func (ec *executionContext) fieldContext_EscalationPolicyStep_escalationPolicy(c return ec.fieldContext_EscalationPolicy_steps(ctx, field) case "notices": return ec.fieldContext_EscalationPolicy_notices(ctx, field) + case "labels": + return ec.fieldContext_EscalationPolicy_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type EscalationPolicy", field.Name) }, @@ -13136,6 +13217,8 @@ func (ec *executionContext) fieldContext_Mutation_createEscalationPolicy(ctx con return ec.fieldContext_EscalationPolicy_steps(ctx, field) case "notices": return ec.fieldContext_EscalationPolicy_notices(ctx, field) + case "labels": + return ec.fieldContext_EscalationPolicy_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type EscalationPolicy", field.Name) }, @@ -13278,6 +13361,8 @@ func (ec *executionContext) fieldContext_Mutation_createRotation(ctx context.Con return ec.fieldContext_Rotation_users(ctx, field) case "nextHandoffTimes": return ec.fieldContext_Rotation_nextHandoffTimes(ctx, field) + case "labels": + return ec.fieldContext_Rotation_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Rotation", field.Name) }, @@ -13541,6 +13626,8 @@ func (ec *executionContext) fieldContext_Mutation_createSchedule(ctx context.Con return ec.fieldContext_Schedule_temporarySchedules(ctx, field) case "onCallNotificationRules": return ec.fieldContext_Schedule_onCallNotificationRules(ctx, field) + case "labels": + return ec.fieldContext_Schedule_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Schedule", field.Name) }, @@ -16714,6 +16801,8 @@ func (ec *executionContext) fieldContext_Query_rotation(ctx context.Context, fie return ec.fieldContext_Rotation_users(ctx, field) case "nextHandoffTimes": return ec.fieldContext_Rotation_nextHandoffTimes(ctx, field) + case "labels": + return ec.fieldContext_Rotation_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Rotation", field.Name) }, @@ -16906,6 +16995,8 @@ func (ec *executionContext) fieldContext_Query_schedule(ctx context.Context, fie return ec.fieldContext_Schedule_temporarySchedules(ctx, field) case "onCallNotificationRules": return ec.fieldContext_Schedule_onCallNotificationRules(ctx, field) + case "labels": + return ec.fieldContext_Schedule_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Schedule", field.Name) }, @@ -17109,6 +17200,8 @@ func (ec *executionContext) fieldContext_Query_escalationPolicy(ctx context.Cont return ec.fieldContext_EscalationPolicy_steps(ctx, field) case "notices": return ec.fieldContext_EscalationPolicy_notices(ctx, field) + case "labels": + return ec.fieldContext_EscalationPolicy_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type EscalationPolicy", field.Name) }, @@ -19213,6 +19306,56 @@ func (ec *executionContext) fieldContext_Rotation_nextHandoffTimes(ctx context.C return fc, nil } +func (ec *executionContext) _Rotation_labels(ctx context.Context, field graphql.CollectedField, obj *rotation.Rotation) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Rotation_labels(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Rotation().Labels(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]label.Label) + fc.Result = res + return ec.marshalNLabel2ᚕgithubᚗcomᚋtargetᚋgoalertᚋlabelᚐLabelᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Rotation_labels(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Rotation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "key": + return ec.fieldContext_Label_key(ctx, field) + case "value": + return ec.fieldContext_Label_value(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Label", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _RotationConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *RotationConnection) (ret graphql.Marshaler) { fc, err := ec.fieldContext_RotationConnection_nodes(ctx, field) if err != nil { @@ -19276,6 +19419,8 @@ func (ec *executionContext) fieldContext_RotationConnection_nodes(ctx context.Co return ec.fieldContext_Rotation_users(ctx, field) case "nextHandoffTimes": return ec.fieldContext_Rotation_nextHandoffTimes(ctx, field) + case "labels": + return ec.fieldContext_Rotation_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Rotation", field.Name) }, @@ -20661,6 +20806,56 @@ func (ec *executionContext) fieldContext_Schedule_onCallNotificationRules(ctx co return fc, nil } +func (ec *executionContext) _Schedule_labels(ctx context.Context, field graphql.CollectedField, obj *schedule.Schedule) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Schedule_labels(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Schedule().Labels(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]label.Label) + fc.Result = res + return ec.marshalNLabel2ᚕgithubᚗcomᚋtargetᚋgoalertᚋlabelᚐLabelᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Schedule_labels(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Schedule", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "key": + return ec.fieldContext_Label_key(ctx, field) + case "value": + return ec.fieldContext_Label_value(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Label", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _ScheduleConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *ScheduleConnection) (ret graphql.Marshaler) { fc, err := ec.fieldContext_ScheduleConnection_nodes(ctx, field) if err != nil { @@ -20722,6 +20917,8 @@ func (ec *executionContext) fieldContext_ScheduleConnection_nodes(ctx context.Co return ec.fieldContext_Schedule_temporarySchedules(ctx, field) case "onCallNotificationRules": return ec.fieldContext_Schedule_onCallNotificationRules(ctx, field) + case "labels": + return ec.fieldContext_Schedule_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Schedule", field.Name) }, @@ -21433,6 +21630,8 @@ func (ec *executionContext) fieldContext_Service_escalationPolicy(ctx context.Co return ec.fieldContext_EscalationPolicy_steps(ctx, field) case "notices": return ec.fieldContext_EscalationPolicy_notices(ctx, field) + case "labels": + return ec.fieldContext_EscalationPolicy_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type EscalationPolicy", field.Name) }, @@ -23966,6 +24165,8 @@ func (ec *executionContext) fieldContext_User_assignedSchedules(ctx context.Cont return ec.fieldContext_Schedule_temporarySchedules(ctx, field) case "onCallNotificationRules": return ec.fieldContext_Schedule_onCallNotificationRules(ctx, field) + case "labels": + return ec.fieldContext_Schedule_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Schedule", field.Name) }, @@ -24251,6 +24452,8 @@ func (ec *executionContext) fieldContext_UserCalendarSubscription_schedule(ctx c return ec.fieldContext_Schedule_temporarySchedules(ctx, field) case "onCallNotificationRules": return ec.fieldContext_Schedule_onCallNotificationRules(ctx, field) + case "labels": + return ec.fieldContext_Schedule_labels(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Schedule", field.Name) }, @@ -33345,6 +33548,42 @@ func (ec *executionContext) _EscalationPolicy(ctx context.Context, sel ast.Selec continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "labels": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._EscalationPolicy_labels(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) @@ -36224,6 +36463,42 @@ func (ec *executionContext) _Rotation(ctx context.Context, sel ast.SelectionSet, continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "labels": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Rotation_labels(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) @@ -36786,6 +37061,42 @@ func (ec *executionContext) _Schedule(ctx context.Context, sel ast.SelectionSet, continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "labels": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Schedule_labels(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) diff --git a/graphql2/graphqlapp/escalationpolicy.go b/graphql2/graphqlapp/escalationpolicy.go index 228fc1df41..670dc415fc 100644 --- a/graphql2/graphqlapp/escalationpolicy.go +++ b/graphql2/graphqlapp/escalationpolicy.go @@ -10,6 +10,7 @@ import ( "github.com/target/goalert/config" "github.com/target/goalert/escalation" "github.com/target/goalert/graphql2" + "github.com/target/goalert/label" "github.com/target/goalert/notice" "github.com/target/goalert/permission" "github.com/target/goalert/search" @@ -344,6 +345,10 @@ func (step *EscalationPolicy) IsFavorite(ctx context.Context, raw *escalation.Po return raw.IsUserFavorite(), nil } +func (ep *EscalationPolicy) Labels(ctx context.Context, raw *escalation.Policy) ([]label.Label, error) { + return ep.LabelStore.FindAllByTarget(ctx, ep.DB, assignment.EscalationPolicyTarget(raw.ID)) +} + func (ep *EscalationPolicy) Steps(ctx context.Context, raw *escalation.Policy) ([]escalation.Step, error) { return ep.PolicyStore.FindAllSteps(ctx, raw.ID) } diff --git a/graphql2/graphqlapp/rotation.go b/graphql2/graphqlapp/rotation.go index a97b02a977..ce4cf02a45 100644 --- a/graphql2/graphqlapp/rotation.go +++ b/graphql2/graphqlapp/rotation.go @@ -7,6 +7,7 @@ import ( "github.com/target/goalert/assignment" "github.com/target/goalert/graphql2" + "github.com/target/goalert/label" "github.com/target/goalert/permission" "github.com/target/goalert/schedule/rotation" "github.com/target/goalert/search" @@ -73,6 +74,10 @@ func (r *Rotation) TimeZone(ctx context.Context, rot *rotation.Rotation) (string return rot.Start.Location().String(), nil } +func (r *Rotation) Labels(ctx context.Context, raw *rotation.Rotation) ([]label.Label, error) { + return r.LabelStore.FindAllByTarget(ctx, r.DB, assignment.RotationTarget(raw.ID)) +} + func (r *Rotation) IsFavorite(ctx context.Context, rot *rotation.Rotation) (bool, error) { return rot.IsUserFavorite(), nil } diff --git a/graphql2/graphqlapp/schedule.go b/graphql2/graphqlapp/schedule.go index d528679e82..0ca657a091 100644 --- a/graphql2/graphqlapp/schedule.go +++ b/graphql2/graphqlapp/schedule.go @@ -13,6 +13,7 @@ import ( "github.com/target/goalert/assignment" "github.com/target/goalert/graphql2" + "github.com/target/goalert/label" "github.com/target/goalert/notificationchannel" "github.com/target/goalert/oncall" "github.com/target/goalert/permission" @@ -82,6 +83,10 @@ func (q *Query) Schedule(ctx context.Context, id string) (*schedule.Schedule, er return (*App)(q).FindOneSchedule(ctx, id) } +func (s *Schedule) Labels(ctx context.Context, raw *schedule.Schedule) ([]label.Label, error) { + return s.LabelStore.FindAllByTarget(ctx, s.DB, assignment.ScheduleTarget(raw.ID)) +} + func (s *Schedule) Shifts(ctx context.Context, raw *schedule.Schedule, start, end time.Time, userIDs []string) ([]oncall.Shift, error) { if end.Before(start) { return nil, validation.NewFieldError("EndTime", "must be after StartTime") diff --git a/graphql2/graphqlapp/service.go b/graphql2/graphqlapp/service.go index 2b0c4e8de3..3fbc98bc2f 100644 --- a/graphql2/graphqlapp/service.go +++ b/graphql2/graphqlapp/service.go @@ -92,7 +92,7 @@ func (s *Service) Notices(ctx context.Context, raw *service.Service) ([]notice.N } func (s *Service) Labels(ctx context.Context, raw *service.Service) ([]label.Label, error) { - return s.LabelStore.FindAllByService(ctx, s.DB, raw.ID) + return s.LabelStore.FindAllByTarget(ctx, s.DB, assignment.ServiceTarget(raw.ID)) } func (s *Service) EscalationPolicy(ctx context.Context, raw *service.Service) (*escalation.Policy, error) { diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql index df1962d70d..c2e3b44104 100644 --- a/graphql2/schema.graphql +++ b/graphql2/schema.graphql @@ -886,6 +886,8 @@ type Schedule { temporarySchedules: [TemporarySchedule!]! onCallNotificationRules: [OnCallNotificationRule!]! + + labels: [Label!]! } input SetScheduleOnCallNotificationRulesInput { @@ -982,6 +984,8 @@ type Rotation { users: [User!]! nextHandoffTimes(num: Int): [ISOTimestamp!]! + + labels: [Label!]! } enum RotationType { @@ -1294,6 +1298,8 @@ type EscalationPolicy { steps: [EscalationPolicyStep!]! notices: [Notice!]! + + labels: [Label!]! } # Different Alert Status. diff --git a/label/store.go b/label/store.go index f9429c563f..ecafd39654 100644 --- a/label/store.go +++ b/label/store.go @@ -96,11 +96,6 @@ func (s *Store) SetTx(ctx context.Context, db gadb.DBTX, label *Label) error { return nil } -// FindAllByService finds all labels for a particular service. It returns all key-value pairs. -func (s *Store) FindAllByService(ctx context.Context, db gadb.DBTX, serviceID string) ([]Label, error) { - return s.FindAllByTarget(ctx, db, assignment.ServiceTarget(serviceID)) -} - // FindAllByTarget finds all labels for a particular target. It returns all key-value pairs. func (s *Store) FindAllByTarget(ctx context.Context, db gadb.DBTX, t assignment.Target) ([]Label, error) { err := permission.LimitCheckAny(ctx, permission.System, permission.User) From 36ab8a3dc30939b89e1bddc76e69b61ab1c44344 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 6 Nov 2023 16:59:08 -0600 Subject: [PATCH 05/11] allow setting labels on creation --- graphql2/generated.go | 33 ++++++++++++++++++++++--- graphql2/graphqlapp/escalationpolicy.go | 9 +++++++ graphql2/graphqlapp/rotation.go | 9 +++++++ graphql2/graphqlapp/schedule.go | 8 ++++++ graphql2/models_gen.go | 19 ++++++++------ graphql2/schema.graphql | 6 +++++ 6 files changed, 73 insertions(+), 11 deletions(-) diff --git a/graphql2/generated.go b/graphql2/generated.go index 2245602b5a..31321cae22 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -28471,7 +28471,7 @@ func (ec *executionContext) unmarshalInputCreateEscalationPolicyInput(ctx contex asMap["repeat"] = 3 } - fieldsInOrder := [...]string{"name", "description", "repeat", "favorite", "steps"} + fieldsInOrder := [...]string{"name", "description", "repeat", "favorite", "steps", "labels"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -28523,6 +28523,15 @@ func (ec *executionContext) unmarshalInputCreateEscalationPolicyInput(ctx contex return it, err } it.Steps = data + case "labels": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("labels")) + data, err := ec.unmarshalOSetLabelInput2ᚕgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐSetLabelInputᚄ(ctx, v) + if err != nil { + return it, err + } + it.Labels = data } } @@ -28764,7 +28773,7 @@ func (ec *executionContext) unmarshalInputCreateRotationInput(ctx context.Contex asMap["shiftLength"] = 1 } - fieldsInOrder := [...]string{"name", "description", "timeZone", "start", "favorite", "type", "shiftLength", "userIDs"} + fieldsInOrder := [...]string{"name", "description", "timeZone", "start", "favorite", "type", "shiftLength", "userIDs", "labels"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -28843,6 +28852,15 @@ func (ec *executionContext) unmarshalInputCreateRotationInput(ctx context.Contex return it, err } it.UserIDs = data + case "labels": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("labels")) + data, err := ec.unmarshalOSetLabelInput2ᚕgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐSetLabelInputᚄ(ctx, v) + if err != nil { + return it, err + } + it.Labels = data } } @@ -28856,7 +28874,7 @@ func (ec *executionContext) unmarshalInputCreateScheduleInput(ctx context.Contex asMap[k] = v } - fieldsInOrder := [...]string{"name", "description", "timeZone", "favorite", "targets", "newUserOverrides"} + fieldsInOrder := [...]string{"name", "description", "timeZone", "favorite", "targets", "newUserOverrides", "labels"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -28917,6 +28935,15 @@ func (ec *executionContext) unmarshalInputCreateScheduleInput(ctx context.Contex return it, err } it.NewUserOverrides = data + case "labels": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("labels")) + data, err := ec.unmarshalOSetLabelInput2ᚕgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐSetLabelInputᚄ(ctx, v) + if err != nil { + return it, err + } + it.Labels = data } } diff --git a/graphql2/graphqlapp/escalationpolicy.go b/graphql2/graphqlapp/escalationpolicy.go index 670dc415fc..ac2da87ef8 100644 --- a/graphql2/graphqlapp/escalationpolicy.go +++ b/graphql2/graphqlapp/escalationpolicy.go @@ -155,6 +155,15 @@ func (m *Mutation) CreateEscalationPolicy(ctx context.Context, input graphql2.Cr return validation.AddPrefix("Steps["+strconv.Itoa(i)+"].", err) } } + + for i, lbl := range input.Labels { + lbl.Target = &assignment.RawTarget{Type: assignment.TargetTypeEscalationPolicy, ID: pol.ID} + _, err = m.SetLabel(ctx, lbl) + if err != nil { + return validation.AddPrefix("labels["+strconv.Itoa(i)+"].", err) + } + } + return err }) diff --git a/graphql2/graphqlapp/rotation.go b/graphql2/graphqlapp/rotation.go index ce4cf02a45..72df7b3dbe 100644 --- a/graphql2/graphqlapp/rotation.go +++ b/graphql2/graphqlapp/rotation.go @@ -3,6 +3,7 @@ package graphqlapp import ( context "context" "database/sql" + "strconv" "time" "github.com/target/goalert/assignment" @@ -64,6 +65,14 @@ func (m *Mutation) CreateRotation(ctx context.Context, input graphql2.CreateRota return err } } + + for i, lbl := range input.Labels { + lbl.Target = &assignment.RawTarget{Type: assignment.TargetTypeRotation, ID: result.ID} + _, err = m.SetLabel(ctx, lbl) + if err != nil { + return validation.AddPrefix("labels["+strconv.Itoa(i)+"].", err) + } + } return err }) diff --git a/graphql2/graphqlapp/schedule.go b/graphql2/graphqlapp/schedule.go index 0ca657a091..2d0c989abc 100644 --- a/graphql2/graphqlapp/schedule.go +++ b/graphql2/graphqlapp/schedule.go @@ -311,6 +311,14 @@ func (m *Mutation) CreateSchedule(ctx context.Context, input graphql2.CreateSche } } + for i, lbl := range input.Labels { + lbl.Target = &assignment.RawTarget{Type: assignment.TargetTypeSchedule, ID: sched.ID} + _, err = m.SetLabel(ctx, lbl) + if err != nil { + return validation.AddPrefix("labels["+strconv.Itoa(i)+"].", err) + } + } + return nil }) diff --git a/graphql2/models_gen.go b/graphql2/models_gen.go index 5b6ce04347..490ce7ac36 100644 --- a/graphql2/models_gen.go +++ b/graphql2/models_gen.go @@ -130,6 +130,7 @@ type CreateEscalationPolicyInput struct { Repeat *int `json:"repeat,omitempty"` Favorite *bool `json:"favorite,omitempty"` Steps []CreateEscalationPolicyStepInput `json:"steps,omitempty"` + Labels []SetLabelInput `json:"labels,omitempty"` } type CreateEscalationPolicyStepInput struct { @@ -161,14 +162,15 @@ type CreateIntegrationKeyInput struct { } type CreateRotationInput struct { - Name string `json:"name"` - Description *string `json:"description,omitempty"` - TimeZone string `json:"timeZone"` - Start time.Time `json:"start"` - Favorite *bool `json:"favorite,omitempty"` - Type rotation.Type `json:"type"` - ShiftLength *int `json:"shiftLength,omitempty"` - UserIDs []string `json:"userIDs,omitempty"` + Name string `json:"name"` + Description *string `json:"description,omitempty"` + TimeZone string `json:"timeZone"` + Start time.Time `json:"start"` + Favorite *bool `json:"favorite,omitempty"` + Type rotation.Type `json:"type"` + ShiftLength *int `json:"shiftLength,omitempty"` + UserIDs []string `json:"userIDs,omitempty"` + Labels []SetLabelInput `json:"labels,omitempty"` } type CreateScheduleInput struct { @@ -178,6 +180,7 @@ type CreateScheduleInput struct { Favorite *bool `json:"favorite,omitempty"` Targets []ScheduleTargetInput `json:"targets,omitempty"` NewUserOverrides []CreateUserOverrideInput `json:"newUserOverrides,omitempty"` + Labels []SetLabelInput `json:"labels,omitempty"` } type CreateServiceInput struct { diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql index c2e3b44104..2bd8684103 100644 --- a/graphql2/schema.graphql +++ b/graphql2/schema.graphql @@ -738,6 +738,8 @@ input CreateScheduleInput { targets: [ScheduleTargetInput!] newUserOverrides: [CreateUserOverrideInput!] + + labels: [SetLabelInput!] } input ScheduleTargetInput { @@ -802,6 +804,8 @@ input CreateEscalationPolicyInput { favorite: Boolean steps: [CreateEscalationPolicyStepInput!] + + labels: [SetLabelInput!] } input CreateEscalationPolicyStepInput { @@ -964,6 +968,8 @@ input CreateRotationInput { shiftLength: Int = 1 userIDs: [ID!] + + labels: [SetLabelInput!] } type Rotation { From a2f8f0ee1f924e93abf6929ed4c76ac594c14e09 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Tue, 7 Nov 2023 10:59:51 -0600 Subject: [PATCH 06/11] allow other types to pass validation --- label/label.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/label/label.go b/label/label.go index 828e6cdb7d..2c1624e9bb 100644 --- a/label/label.go +++ b/label/label.go @@ -15,7 +15,12 @@ type Label struct { // Normalize will validate and normalize the label, returning a copy. func (l Label) Normalize() (*Label, error) { return &l, validate.Many( - validate.OneOf("TargetType", l.Target.TargetType(), assignment.TargetTypeService), + validate.OneOf("TargetType", l.Target.TargetType(), + assignment.TargetTypeService, + assignment.TargetTypeEscalationPolicy, + assignment.TargetTypeSchedule, + assignment.TargetTypeRotation, + ), validate.UUID("TargetID", l.Target.TargetID()), validate.LabelKey("Key", l.Key), validate.LabelValue("Value", l.Value), From 4d5b74b576bdccb214390b60db4c077b5a43511a Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 13 Nov 2023 12:20:48 -0600 Subject: [PATCH 07/11] remove not null constraint on tgt_service_id --- .../20231106152429-schedule-rotation-ep-labels.sql | 11 +++++++++-- migrate/schema.sql | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/migrate/migrations/20231106152429-schedule-rotation-ep-labels.sql b/migrate/migrations/20231106152429-schedule-rotation-ep-labels.sql index 965553fb19..359004545b 100644 --- a/migrate/migrations/20231106152429-schedule-rotation-ep-labels.sql +++ b/migrate/migrations/20231106152429-schedule-rotation-ep-labels.sql @@ -3,7 +3,8 @@ ALTER TABLE labels ADD COLUMN tgt_schedule_id uuid REFERENCES schedules(id) ON DELETE CASCADE, ADD COLUMN tgt_rotation_id uuid REFERENCES rotations(id) ON DELETE CASCADE, ADD COLUMN tgt_ep_id uuid REFERENCES escalation_policies(id) ON DELETE CASCADE, - DROP CONSTRAINT labels_tgt_service_id_key_key; + DROP CONSTRAINT labels_tgt_service_id_key_key, + ALTER COLUMN tgt_service_id DROP NOT NULL; -- drop redundant index DROP INDEX idx_labels_service_id; @@ -11,11 +12,17 @@ DROP INDEX idx_labels_service_id; CREATE UNIQUE INDEX idx_labels_resource_id ON labels(tgt_service_id, tgt_schedule_id, tgt_rotation_id, tgt_ep_id, key); -- +migrate Down +-- Since we're re-adding the NOT NULL constraint, we need to make sure there are no NULL values +-- in the column. We can do this by deleting all rows where the service ID is NULL. +DELETE FROM labels +WHERE tgt_service_id IS NULL; + ALTER TABLE labels DROP COLUMN tgt_schedule_id, DROP COLUMN tgt_rotation_id, DROP COLUMN tgt_ep_id, - ADD CONSTRAINT labels_tgt_service_id_key_key UNIQUE (tgt_service_id, key); + ADD CONSTRAINT labels_tgt_service_id_key_key UNIQUE (tgt_service_id, key), + ALTER COLUMN tgt_service_id SET NOT NULL; CREATE INDEX idx_labels_service_id ON labels(tgt_service_id); diff --git a/migrate/schema.sql b/migrate/schema.sql index 35b2f5d422..93a81600b8 100644 --- a/migrate/schema.sql +++ b/migrate/schema.sql @@ -1,5 +1,5 @@ -- This file is auto-generated by "make db-schema"; DO NOT EDIT --- DATA=256a801de96f5b86d70f023e7324db7027f6e0f74ab8bc747a408794441e63b6 - +-- DATA=31ffe71cdf389b96f982d11ebff4ef0a98247bdd37653116808f8f0c7354d964 - -- DISK=11c6733e21df09221d04b58a363c2687b47269bd2b8092be6b2a8120d8e366b7 - -- PSQL=11c6733e21df09221d04b58a363c2687b47269bd2b8092be6b2a8120d8e366b7 - -- @@ -1695,7 +1695,7 @@ CREATE TABLE labels ( tgt_ep_id uuid, tgt_rotation_id uuid, tgt_schedule_id uuid, - tgt_service_id uuid NOT NULL, + tgt_service_id uuid, value text NOT NULL, CONSTRAINT labels_pkey PRIMARY KEY (id), CONSTRAINT labels_tgt_ep_id_fkey FOREIGN KEY (tgt_ep_id) REFERENCES escalation_policies(id) ON DELETE CASCADE, From 2ac82d1edd2bee8dd60348c3fb39dffb441c51cd Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 27 Nov 2023 13:44:39 -0600 Subject: [PATCH 08/11] regen --- gadb/models.go | 2 +- web/src/schema.d.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gadb/models.go b/gadb/models.go index 801a3b7912..9ceff6d90c 100644 --- a/gadb/models.go +++ b/gadb/models.go @@ -958,7 +958,7 @@ type Label struct { TgtEpID uuid.NullUUID TgtRotationID uuid.NullUUID TgtScheduleID uuid.NullUUID - TgtServiceID uuid.UUID + TgtServiceID uuid.NullUUID Value string } diff --git a/web/src/schema.d.ts b/web/src/schema.d.ts index 01f3032cb2..256988a558 100644 --- a/web/src/schema.d.ts +++ b/web/src/schema.d.ts @@ -572,6 +572,7 @@ export interface CreateScheduleInput { favorite?: null | boolean targets?: null | ScheduleTargetInput[] newUserOverrides?: null | CreateUserOverrideInput[] + labels?: null | SetLabelInput[] } export interface ScheduleTargetInput { @@ -627,6 +628,7 @@ export interface CreateEscalationPolicyInput { repeat?: null | number favorite?: null | boolean steps?: null | CreateEscalationPolicyStepInput[] + labels?: null | SetLabelInput[] } export interface CreateEscalationPolicyStepInput { @@ -706,6 +708,7 @@ export interface Schedule { isFavorite: boolean temporarySchedules: TemporarySchedule[] onCallNotificationRules: OnCallNotificationRule[] + labels: Label[] } export interface SetScheduleOnCallNotificationRulesInput { @@ -764,6 +767,7 @@ export interface CreateRotationInput { type: RotationType shiftLength?: null | number userIDs?: null | string[] + labels?: null | SetLabelInput[] } export interface Rotation { @@ -779,6 +783,7 @@ export interface Rotation { userIDs: string[] users: User[] nextHandoffTimes: ISOTimestamp[] + labels: Label[] } export type RotationType = 'monthly' | 'weekly' | 'daily' | 'hourly' @@ -1031,6 +1036,7 @@ export interface EscalationPolicy { assignedTo: Target[] steps: EscalationPolicyStep[] notices: Notice[] + labels: Label[] } export type AlertStatus = From ad395c31f22833acc0d1132d4f4f6af85d50a0d5 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 27 Nov 2023 16:32:11 -0600 Subject: [PATCH 09/11] force id to use sequence --- test/smoke/graphqlservicelabels_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/smoke/graphqlservicelabels_test.go b/test/smoke/graphqlservicelabels_test.go index afd7ef6987..d73d0f5719 100644 --- a/test/smoke/graphqlservicelabels_test.go +++ b/test/smoke/graphqlservicelabels_test.go @@ -23,9 +23,9 @@ func TestGraphQLServiceLabels(t *testing.T) { values ({{uuid "sid"}}, {{uuid "eid"}}, 'service'); - insert into labels (id, tgt_service_id, key, value) + insert into labels (tgt_service_id, key, value) values - ('1', {{uuid "sid"}}, 'foo/bar', 'testvalue'); + ({{uuid "sid"}}, 'foo/bar', 'testvalue'); ` h := harness.NewHarness(t, sql, "labels-switchover-trigger") From b205b09487e900e66aed9d1ed746a4648ad8ec8f Mon Sep 17 00:00:00 2001 From: Nathaniel Cook Date: Thu, 28 Dec 2023 09:45:48 -0800 Subject: [PATCH 10/11] make check --- graphql2/generated.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/graphql2/generated.go b/graphql2/generated.go index 999b6c1598..91cd19e4e6 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -30227,8 +30227,6 @@ func (ec *executionContext) unmarshalInputCreateEscalationPolicyInput(ctx contex } it.Steps = data case "labels": - var err error - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("labels")) data, err := ec.unmarshalOSetLabelInput2ᚕgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐSetLabelInputᚄ(ctx, v) if err != nil { @@ -30508,8 +30506,6 @@ func (ec *executionContext) unmarshalInputCreateRotationInput(ctx context.Contex } it.UserIDs = data case "labels": - var err error - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("labels")) data, err := ec.unmarshalOSetLabelInput2ᚕgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐSetLabelInputᚄ(ctx, v) if err != nil { @@ -30579,8 +30575,6 @@ func (ec *executionContext) unmarshalInputCreateScheduleInput(ctx context.Contex } it.NewUserOverrides = data case "labels": - var err error - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("labels")) data, err := ec.unmarshalOSetLabelInput2ᚕgithubᚗcomᚋtargetᚋgoalertᚋgraphql2ᚐSetLabelInputᚄ(ctx, v) if err != nil { From 8af00f0d57d63cddc3ab099e65c0164d20de65ba Mon Sep 17 00:00:00 2001 From: Forfold Date: Tue, 14 May 2024 08:19:38 -0700 Subject: [PATCH 11/11] make check --- graphql2/generated.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphql2/generated.go b/graphql2/generated.go index 910317e261..cefac75f79 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -12207,7 +12207,7 @@ func (ec *executionContext) _EscalationPolicy_labels(ctx context.Context, field return ec.marshalNLabel2ᚕgithubᚗcomᚋtargetᚋgoalertᚋlabelᚐLabelᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_EscalationPolicy_labels(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_EscalationPolicy_labels(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "EscalationPolicy", Field: field, @@ -23828,7 +23828,7 @@ func (ec *executionContext) _Rotation_labels(ctx context.Context, field graphql. return ec.marshalNLabel2ᚕgithubᚗcomᚋtargetᚋgoalertᚋlabelᚐLabelᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Rotation_labels(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Rotation_labels(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Rotation", Field: field, @@ -25330,7 +25330,7 @@ func (ec *executionContext) _Schedule_labels(ctx context.Context, field graphql. return ec.marshalNLabel2ᚕgithubᚗcomᚋtargetᚋgoalertᚋlabelᚐLabelᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Schedule_labels(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Schedule_labels(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Schedule", Field: field,