diff --git a/build/internal/cmd/generate/main.go b/build/internal/cmd/generate/main.go index 14b30fd8d0..782f17d16d 100644 --- a/build/internal/cmd/generate/main.go +++ b/build/internal/cmd/generate/main.go @@ -73,8 +73,10 @@ func main() { for k := 0; k < *flagRuleCount; k++ { rule := &ext.Rule{ - Rank: uint(k + 1), - SegmentKey: doc.Segments[k%len(doc.Segments)].Key, + Rank: uint(k + 1), + Segment: &ext.SegmentEmbed{ + IsSegment: ext.SegmentKey(doc.Segments[k%len(doc.Segments)].Key), + }, } for l := 0; l < *flagRuleDistCount; l++ { diff --git a/build/testing/integration.go b/build/testing/integration.go index f349209512..d241c2b70b 100644 --- a/build/testing/integration.go +++ b/build/testing/integration.go @@ -295,7 +295,7 @@ func importExport(ctx context.Context, _ *dagger.Client, base, flipt *dagger.Con if namespace == "" { namespace = "default" // replace namespace in expected yaml - expected = strings.ReplaceAll(expected, "version: \"1.1\"\n", fmt.Sprintf("version: \"1.1\"\nnamespace: %s\n", namespace)) + expected = strings.ReplaceAll(expected, "version: \"1.2\"\n", fmt.Sprintf("version: \"1.2\"\nnamespace: %s\n", namespace)) } // use target flipt binary to invoke import diff --git a/build/testing/integration/api/api.go b/build/testing/integration/api/api.go index 2774f0c9fe..23daa6c74f 100644 --- a/build/testing/integration/api/api.go +++ b/build/testing/integration/api/api.go @@ -351,6 +351,64 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.Equal(t, "eq", updatedConstraint.Operator) assert.Equal(t, "baz", updatedConstraint.Value) assert.Equal(t, "newdesc", updatedConstraint.Description) + + t.Log(`Create two additional segments.`) + + createdSegment, err = client.Flipt().CreateSegment(ctx, &flipt.CreateSegmentRequest{ + NamespaceKey: namespace, + Key: "segment", + Name: "Segment", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + }) + + require.NoError(t, err) + + assert.Equal(t, "segment", createdSegment.Key) + assert.Equal(t, "Segment", createdSegment.Name) + assert.Equal(t, flipt.MatchType_ALL_MATCH_TYPE, createdSegment.MatchType) + + createdConstraint, err := client.Flipt().CreateConstraint(ctx, &flipt.CreateConstraintRequest{ + NamespaceKey: namespace, + SegmentKey: createdSegment.Key, + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "first", + Operator: "eq", + Value: "segment", + }) + + require.NoError(t, err) + + assert.Equal(t, "first", createdConstraint.Property) + assert.Equal(t, "eq", createdConstraint.Operator) + assert.Equal(t, "segment", createdConstraint.Value) + + createdSegment, err = client.Flipt().CreateSegment(ctx, &flipt.CreateSegmentRequest{ + NamespaceKey: namespace, + Key: "another-segment", + Name: "Another Segment", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + }) + + require.NoError(t, err) + + assert.Equal(t, "another-segment", createdSegment.Key) + assert.Equal(t, "Another Segment", createdSegment.Name) + assert.Equal(t, flipt.MatchType_ALL_MATCH_TYPE, createdSegment.MatchType) + + createdConstraint, err = client.Flipt().CreateConstraint(ctx, &flipt.CreateConstraintRequest{ + NamespaceKey: namespace, + SegmentKey: createdSegment.Key, + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "second", + Operator: "eq", + Value: "another-segment", + }) + + require.NoError(t, err) + + assert.Equal(t, "second", createdConstraint.Property) + assert.Equal(t, "eq", createdConstraint.Operator) + assert.Equal(t, "another-segment", createdConstraint.Value) }) t.Run("Rules and Distributions", func(t *testing.T) { @@ -397,6 +455,35 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.Equal(t, "everyone", ruleTwo.SegmentKey) assert.Equal(t, int32(2), ruleTwo.Rank) + t.Log(`Create rule "rank 3".`) + + ruleThree, err := client.Flipt().CreateRule(ctx, &flipt.CreateRuleRequest{ + NamespaceKey: namespace, + FlagKey: "test", + SegmentKey: "another-segment", + Rank: 3, + }) + + require.NoError(t, err) + + assert.Equal(t, "test", ruleThree.FlagKey) + assert.Equal(t, "another-segment", ruleThree.SegmentKey) + assert.Equal(t, int32(3), ruleThree.Rank) + + updatedRuleThree, err := client.Flipt().UpdateRule(ctx, &flipt.UpdateRuleRequest{ + Id: ruleThree.Id, + NamespaceKey: namespace, + FlagKey: "test", + SegmentKeys: []string{"segment", "another-segment"}, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + }) + require.NoError(t, err) + + assert.Equal(t, "test", updatedRuleThree.FlagKey) + assert.Contains(t, updatedRuleThree.SegmentKeys, "another-segment") + assert.Contains(t, updatedRuleThree.SegmentKeys, "segment") + assert.Equal(t, int32(3), updatedRuleThree.Rank) + // ensure you can not link flags and segments from different namespaces. if !namespaceIsDefault(namespace) { t.Log(`Ensure that rules can only link entities in the same namespace.`) @@ -428,17 +515,18 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au }) require.NoError(t, err) - assert.Len(t, allRules.Rules, 2) + assert.Len(t, allRules.Rules, 3) assert.Equal(t, ruleOne.Id, allRules.Rules[0].Id) assert.Equal(t, ruleTwo.Id, allRules.Rules[1].Id) + assert.Equal(t, ruleThree.Id, allRules.Rules[2].Id) t.Log(`Re-order rules.`) err = client.Flipt().OrderRules(ctx, &flipt.OrderRulesRequest{ NamespaceKey: namespace, FlagKey: "test", - RuleIds: []string{ruleTwo.Id, ruleOne.Id}, + RuleIds: []string{ruleTwo.Id, ruleOne.Id, ruleThree.Id}, }) require.NoError(t, err) @@ -450,13 +538,15 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au }) require.NoError(t, err) - assert.Len(t, allRules.Rules, 2) + assert.Len(t, allRules.Rules, 3) // ensure the order has switched assert.Equal(t, ruleTwo.Id, allRules.Rules[0].Id) assert.Equal(t, int32(1), allRules.Rules[0].Rank) assert.Equal(t, ruleOne.Id, allRules.Rules[1].Id) assert.Equal(t, int32(2), allRules.Rules[1].Rank) + assert.Equal(t, ruleThree.Id, allRules.Rules[2].Id) + assert.Equal(t, int32(3), allRules.Rules[2].Rank) t.Log(`Create distribution "rollout 100".`) @@ -533,6 +623,15 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.Equal(t, ruleTwo.Id, distribution.RuleId) assert.Equal(t, float32(100), distribution.Rollout) + + _, err = client.Flipt().CreateDistribution(ctx, &flipt.CreateDistributionRequest{ + NamespaceKey: namespace, + FlagKey: "test", + RuleId: ruleThree.Id, + VariantId: flag.Variants[0].Id, + Rollout: 100, + }) + require.NoError(t, err) }) t.Run("Boolean Rollouts", func(t *testing.T) { @@ -572,11 +671,51 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.Equal(t, "everyone", rolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.SegmentKey) assert.Equal(t, true, rolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.Value) + anotherRolloutSegment, err := client.Flipt().CreateRollout(ctx, &flipt.CreateRolloutRequest{ + NamespaceKey: namespace, + FlagKey: "boolean_disabled", + Description: "matches a segment", + Rank: 2, + Rule: &flipt.CreateRolloutRequest_Segment{ + Segment: &flipt.RolloutSegment{ + SegmentKey: "another-segment", + Value: false, + }, + }, + }) + require.NoError(t, err) + + assert.Equal(t, namespace, anotherRolloutSegment.NamespaceKey) + assert.Equal(t, "boolean_disabled", anotherRolloutSegment.FlagKey) + assert.Equal(t, int32(2), anotherRolloutSegment.Rank) + assert.Equal(t, "another-segment", anotherRolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.SegmentKey) + assert.Equal(t, false, anotherRolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.Value) + + updatedAnotherRolloutSegment, err := client.Flipt().UpdateRollout(ctx, &flipt.UpdateRolloutRequest{ + Id: anotherRolloutSegment.Id, + NamespaceKey: namespace, + FlagKey: "boolean_disabled", + Rule: &flipt.UpdateRolloutRequest_Segment{ + Segment: &flipt.RolloutSegment{ + SegmentKeys: []string{"another-segment", "segment"}, + Value: false, + }, + }, + }) + require.NoError(t, err) + + assert.Equal(t, namespace, updatedAnotherRolloutSegment.NamespaceKey) + assert.Equal(t, "boolean_disabled", updatedAnotherRolloutSegment.FlagKey) + assert.Equal(t, int32(2), updatedAnotherRolloutSegment.Rank) + assert.Contains(t, updatedAnotherRolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.SegmentKeys, "segment") + assert.Contains(t, updatedAnotherRolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.SegmentKeys, "another-segment") + assert.Equal(t, false, updatedAnotherRolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.Value) + rolloutThreshold, err := client.Flipt().CreateRollout(ctx, &flipt.CreateRolloutRequest{ NamespaceKey: namespace, FlagKey: "boolean_disabled", Description: "50% disabled", - Rank: 2, + Rank: 3, Rule: &flipt.CreateRolloutRequest_Threshold{ Threshold: &flipt.RolloutThreshold{ Percentage: 50, @@ -589,7 +728,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.Equal(t, namespace, rolloutThreshold.NamespaceKey) assert.Equal(t, "boolean_disabled", rolloutThreshold.FlagKey) assert.Equal(t, "50% disabled", rolloutThreshold.Description) - assert.Equal(t, int32(2), rolloutThreshold.Rank) + assert.Equal(t, int32(3), rolloutThreshold.Rank) assert.Equal(t, float32(50.0), rolloutThreshold.Rule.(*flipt.Rollout_Threshold).Threshold.Percentage) assert.Equal(t, true, rolloutThreshold.Rule.(*flipt.Rollout_Threshold).Threshold.Value) @@ -635,6 +774,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.Empty(t, cmp.Diff([]*flipt.Rollout{ rolloutSegment, + updatedAnotherRolloutSegment, rolloutThreshold, }, rollouts.Rules, protocmp.Transform())) @@ -669,7 +809,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.Equal(t, namespace, updatedRollout.NamespaceKey) assert.Equal(t, "boolean_disabled", updatedRollout.FlagKey) assert.Equal(t, "50% enabled", updatedRollout.Description) - assert.Equal(t, int32(2), updatedRollout.Rank) + assert.Equal(t, int32(3), updatedRollout.Rank) assert.Equal(t, float32(50.0), updatedRollout.Rule.(*flipt.Rollout_Threshold).Threshold.Percentage) assert.Equal(t, false, updatedRollout.Rule.(*flipt.Rollout_Threshold).Threshold.Value) @@ -779,7 +919,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au t.Run("Evaluation", func(t *testing.T) { t.Run("Variant", func(t *testing.T) { - t.Run("successful match", func(t *testing.T) { + t.Run("successful match (rank 1)", func(t *testing.T) { result, err := client.Evaluation().Variant(ctx, &evaluation.EvaluationRequest{ NamespaceKey: namespace, FlagKey: "test", @@ -797,6 +937,25 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason) }) + t.Run("successful match (rank 3)", func(t *testing.T) { + result, err := client.Evaluation().Variant(ctx, &evaluation.EvaluationRequest{ + NamespaceKey: namespace, + FlagKey: "test", + EntityId: uuid.Must(uuid.NewV4()).String(), + Context: map[string]string{ + "first": "segment", + "second": "another-segment", + }, + }) + require.NoError(t, err) + + require.True(t, result.Match, "Evaluation should have matched.") + assert.Contains(t, result.SegmentKeys, "segment") + assert.Contains(t, result.SegmentKeys, "another-segment") + assert.Equal(t, "one", result.VariantKey) + assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason) + }) + t.Run("no match", func(t *testing.T) { result, err := client.Evaluation().Variant(ctx, &evaluation.EvaluationRequest{ NamespaceKey: namespace, @@ -875,7 +1034,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.False(t, result.Enabled, "value should be threshold match value") }) - t.Run("segment match", func(t *testing.T) { + t.Run("segment match (rank 1)", func(t *testing.T) { result, err := client.Evaluation().Boolean(ctx, &evaluation.EvaluationRequest{ NamespaceKey: namespace, FlagKey: "boolean_disabled", @@ -891,6 +1050,23 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason) assert.True(t, result.Enabled, "value should be segment match value") }) + + t.Run("segment match (rank 2)", func(t *testing.T) { + result, err := client.Evaluation().Boolean(ctx, &evaluation.EvaluationRequest{ + NamespaceKey: namespace, + FlagKey: "boolean_disabled", + EntityId: "fixed", + Context: map[string]string{ + "first": "segment", + "second": "another-segment", + }, + }) + + require.NoError(t, err) + + assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason) + assert.False(t, result.Enabled, "value should be segment match value") + }) }) t.Run("Batch", func(t *testing.T) { @@ -999,6 +1175,21 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au NamespaceKey: namespace, Key: "everyone", }) + + require.NoError(t, err) + + err = client.Flipt().DeleteSegment(ctx, &flipt.DeleteSegmentRequest{ + NamespaceKey: namespace, + Key: "segment", + }) + + require.NoError(t, err) + + err = client.Flipt().DeleteSegment(ctx, &flipt.DeleteSegmentRequest{ + NamespaceKey: namespace, + Key: "another-segment", + }) + require.NoError(t, err) if !namespaceIsDefault(namespace) { diff --git a/build/testing/integration/readonly/readonly_test.go b/build/testing/integration/readonly/readonly_test.go index 25e2bcbe12..6031cc6177 100644 --- a/build/testing/integration/readonly/readonly_test.go +++ b/build/testing/integration/readonly/readonly_test.go @@ -101,7 +101,7 @@ func TestReadOnly(t *testing.T) { NamespaceKey: namespace, }) require.NoError(t, err) - require.Len(t, flags.Flags, 53) + require.Len(t, flags.Flags, 55) flag := flags.Flags[0] assert.Equal(t, namespace, flag.NamespaceKey) @@ -129,7 +129,7 @@ func TestReadOnly(t *testing.T) { if flags.NextPageToken == "" { // ensure last page contains 3 entries (boolean and disabled) - assert.Len(t, flags.Flags, 3) + assert.Len(t, flags.Flags, 5) found = append(found, flags.Flags...) @@ -144,7 +144,7 @@ func TestReadOnly(t *testing.T) { nextPage = flags.NextPageToken } - require.Len(t, found, 53) + require.Len(t, found, 55) }) }) @@ -189,7 +189,7 @@ func TestReadOnly(t *testing.T) { }) require.NoError(t, err) - require.Len(t, segments.Segments, 51) + require.Len(t, segments.Segments, 52) t.Run("Paginated (page size 10)", func(t *testing.T) { var ( @@ -209,7 +209,7 @@ func TestReadOnly(t *testing.T) { found = append(found, segments.Segments...) if segments.NextPageToken == "" { - assert.Len(t, segments.Segments, 1) + assert.Len(t, segments.Segments, 2) break } @@ -218,7 +218,7 @@ func TestReadOnly(t *testing.T) { nextPage = segments.NextPageToken } - require.Len(t, found, 51) + require.Len(t, found, 52) }) }) @@ -337,6 +337,7 @@ func TestReadOnly(t *testing.T) { assert.Equal(t, namespace, rule.NamespaceKey) assert.Equal(t, "flag_boolean", rule.FlagKey) assert.Equal(t, "segment_001", rule.GetSegment().SegmentKey) + assert.True(t, rule.GetSegment().Value) assert.NotEmpty(t, rule.Id) assert.Equal(t, int32(1), rule.Rank) @@ -441,6 +442,26 @@ func TestReadOnly(t *testing.T) { assert.Equal(t, true, response.Match) assert.Equal(t, "variant_002", response.VariantKey) assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, response.Reason) + assert.Contains(t, response.SegmentKeys, "segment_005") + }) + + t.Run("match segment ANDing", func(t *testing.T) { + response, err := sdk.Evaluation().Variant(ctx, &evaluation.EvaluationRequest{ + NamespaceKey: namespace, + FlagKey: "flag_variant_and_segments", + EntityId: "some-fixed-entity-id", + Context: map[string]string{ + "in_segment": "segment_001", + "anding": "segment", + }, + }) + require.NoError(t, err) + + assert.Equal(t, true, response.Match) + assert.Equal(t, "variant_002", response.VariantKey) + assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, response.Reason) + assert.Contains(t, response.SegmentKeys, "segment_001") + assert.Contains(t, response.SegmentKeys, "segment_anding") }) t.Run("no match", func(t *testing.T) { @@ -543,6 +564,21 @@ func TestReadOnly(t *testing.T) { assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason) assert.True(t, result.Enabled, "segment evaluation value should be true") }) + + t.Run("segment with ANDing", func(t *testing.T) { + result, err := sdk.Evaluation().Boolean(ctx, &evaluation.EvaluationRequest{ + NamespaceKey: namespace, + FlagKey: "flag_boolean_and_segments", + Context: map[string]string{ + "in_segment": "segment_001", + "anding": "segment", + }, + }) + require.NoError(t, err) + + assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason) + assert.True(t, result.Enabled, "segment evaluation value should be true") + }) }) t.Run("Batch", func(t *testing.T) { diff --git a/build/testing/integration/readonly/testdata/default.yaml b/build/testing/integration/readonly/testdata/default.yaml index 9d50804436..dba23c1c56 100644 --- a/build/testing/integration/readonly/testdata/default.yaml +++ b/build/testing/integration/readonly/testdata/default.yaml @@ -1,4 +1,4 @@ -version: "1.1" +version: "1.2" flags: - key: flag_001 name: FLAG_001 @@ -15550,6 +15550,27 @@ flags: rollout: 50 - variant: variant_002 rollout: 50 +- key: flag_variant_and_segments + name: FLAG_VARIANT_AND_SEGMENTS + type: VARIANT_FLAG_TYPE + description: And segments for variant flags + enabled: true + variants: + - key: variant_001 + name: VARIANT_001 + - key: variant_002 + name: VARIANT_002 + rules: + - segment: + keys: + - segment_001 + - segment_anding + operator: AND_SEGMENT_OPERATOR + distributions: + - variant: variant_001 + rollout: 50 + - variant: variant_002 + rollout: 50 - key: flag_boolean name: FLAG_BOOLEAN type: BOOLEAN_FLAG_TYPE @@ -15574,6 +15595,19 @@ flags: threshold: percentage: 50 value: true +- key: flag_boolean_and_segments + name: FLAG_BOOLEAN_AND_SEGMENTS + type: BOOLEAN_FLAG_TYPE + description: And segments for boolean flags + enabled: false + rollouts: + - description: enabled for segment_001 + segment: + keys: + - segment_001 + - segment_anding + operator: AND_SEGMENT_OPERATOR + value: true - key: flag_disabled name: FLAG_DISABLED type: VARIANT_FLAG_TYPE @@ -16244,3 +16278,12 @@ segments: name: SEGMENT_NO_CONSTRAINTS description: Some Segment Description match_type: ALL_MATCH_TYPE +- key: segment_anding + name: SEGMENT_ANDING + description: Some Segment Description + constraints: + - type: STRING_COMPARISON_TYPE + property: anding + operator: eq + value: segment + match_type: ALL_MATCH_TYPE diff --git a/build/testing/integration/readonly/testdata/production.yaml b/build/testing/integration/readonly/testdata/production.yaml index 2736877ef3..638518afc6 100644 --- a/build/testing/integration/readonly/testdata/production.yaml +++ b/build/testing/integration/readonly/testdata/production.yaml @@ -1,4 +1,4 @@ -version: "1.1" +version: "1.2" namespace: production flags: - key: flag_001 @@ -15551,6 +15551,27 @@ flags: rollout: 50 - variant: variant_002 rollout: 50 +- key: flag_variant_and_segments + name: FLAG_VARIANT_AND_SEGMENTS + type: VARIANT_FLAG_TYPE + description: And segments for variant flags + enabled: true + variants: + - key: variant_001 + name: VARIANT_001 + - key: variant_002 + name: VARIANT_002 + rules: + - segment: + keys: + - segment_001 + - segment_anding + operator: AND_SEGMENT_OPERATOR + distributions: + - variant: variant_001 + rollout: 50 + - variant: variant_002 + rollout: 50 - key: flag_boolean name: FLAG_BOOLEAN type: BOOLEAN_FLAG_TYPE @@ -15575,6 +15596,19 @@ flags: threshold: percentage: 50 value: true +- key: flag_boolean_and_segments + name: FLAG_BOOLEAN + type: BOOLEAN_FLAG_TYPE + description: And segments for boolean flags + enabled: false + rollouts: + - description: enabled for segment_001 + segment: + keys: + - segment_001 + - segment_anding + operator: AND_SEGMENT_OPERATOR + value: true - key: flag_disabled name: FLAG_DISABLED type: VARIANT_FLAG_TYPE @@ -16245,3 +16279,12 @@ segments: name: SEGMENT_NO_CONSTRAINTS description: Some Segment Description match_type: ALL_MATCH_TYPE +- key: segment_anding + name: SEGMENT_ANDING + description: Some Segment Description + constraints: + - type: STRING_COMPARISON_TYPE + property: anding + operator: eq + value: segment + match_type: ALL_MATCH_TYPE diff --git a/config/migrations/cockroachdb/8_segment_anding_tables.up.sql b/config/migrations/cockroachdb/8_segment_anding_tables.up.sql new file mode 100644 index 0000000000..f974fb7fb7 --- /dev/null +++ b/config/migrations/cockroachdb/8_segment_anding_tables.up.sql @@ -0,0 +1,21 @@ +-- Rules +CREATE TABLE IF NOT EXISTS rule_segments ( + rule_id VARCHAR(255) NOT NULL REFERENCES rules ON DELETE CASCADE, + namespace_key VARCHAR(255) NOT NULL, + segment_key VARCHAR(255) NOT NULL, + UNIQUE (rule_id, namespace_key, segment_key), + FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, key) ON DELETE CASCADE +); + +INSERT INTO rule_segments (rule_id, namespace_key, segment_key) SELECT id AS rule_id, namespace_key, segment_key FROM rules; + +-- Rollouts +CREATE TABLE IF NOT EXISTS rollout_segment_references ( + rollout_segment_id VARCHAR(255) NOT NULL REFERENCES rollout_segments ON DELETE CASCADE, + namespace_key VARCHAR(255) NOT NULL, + segment_key VARCHAR(255) NOT NULL, + UNIQUE (rollout_segment_id, namespace_key, segment_key), + FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, key) ON DELETE CASCADE +); + +INSERT INTO rollout_segment_references (rollout_segment_id, namespace_key, segment_key) SELECT id AS rollout_segment_id, namespace_key, segment_key FROM rollout_segments; \ No newline at end of file diff --git a/config/migrations/cockroachdb/9_alter_rules_rollouts_segments.up.sql b/config/migrations/cockroachdb/9_alter_rules_rollouts_segments.up.sql new file mode 100644 index 0000000000..036220bb21 --- /dev/null +++ b/config/migrations/cockroachdb/9_alter_rules_rollouts_segments.up.sql @@ -0,0 +1,15 @@ +-- Rules +ALTER TABLE IF EXISTS rules DROP CONSTRAINT fk_namespace_key_ref_segments; + +ALTER TABLE IF EXISTS rules DROP COLUMN segment_key; + +ALTER TABLE IF EXISTS rules ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0; + +-- Rollouts +ALTER TABLE IF EXISTS rollout_segments DROP CONSTRAINT fk_namespace_key_ref_segments; +ALTER TABLE IF EXISTS rollout_segments DROP CONSTRAINT fk_namespace_key_ref_namespaces; + +ALTER TABLE IF EXISTS rollout_segments DROP COLUMN segment_key; +ALTER TABLE IF EXISTS rollout_segments DROP COLUMN namespace_key; + +ALTER TABLE IF EXISTS rollout_segments ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/config/migrations/mysql/10_alter_rules_rollout_segments.up.sql b/config/migrations/mysql/10_alter_rules_rollout_segments.up.sql new file mode 100644 index 0000000000..55f30f3c64 --- /dev/null +++ b/config/migrations/mysql/10_alter_rules_rollout_segments.up.sql @@ -0,0 +1,15 @@ +-- Rules +ALTER TABLE rules DROP FOREIGN KEY `rules_ibfk_3`; + +ALTER TABLE rules DROP COLUMN segment_key; + +ALTER TABLE rules ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0; + +-- Rollouts +ALTER TABLE rollout_segments DROP FOREIGN KEY `rollout_segments_ibfk_1`; +ALTER TABLE rollout_segments DROP FOREIGN KEY `rollout_segments_ibfk_3`; + +ALTER TABLE rollout_segments DROP COLUMN segment_key; +ALTER TABLE rollout_segments DROP COLUMN namespace_key; + +ALTER TABLE rollout_segments ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/config/migrations/mysql/9_segment_anding_tables.up.sql b/config/migrations/mysql/9_segment_anding_tables.up.sql new file mode 100644 index 0000000000..5eb77470d5 --- /dev/null +++ b/config/migrations/mysql/9_segment_anding_tables.up.sql @@ -0,0 +1,23 @@ +-- Rules +CREATE TABLE IF NOT EXISTS rule_segments ( + rule_id VARCHAR(255) NOT NULL, + namespace_key VARCHAR(255) NOT NULL, + segment_key VARCHAR(255) NOT NULL, + CONSTRAINT rule_id_namespace_segment UNIQUE (rule_id, namespace_key, segment_key), + FOREIGN KEY (rule_id) REFERENCES rules (id) ON DELETE CASCADE, + FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, `key`) ON DELETE CASCADE +); + +INSERT INTO rule_segments (rule_id, namespace_key, segment_key) SELECT id AS rule_id, namespace_key, segment_key FROM rules; + +-- Rollouts +CREATE TABLE IF NOT EXISTS rollout_segment_references ( + rollout_segment_id VARCHAR(255) NOT NULL, + namespace_key VARCHAR(255) NOT NULL, + segment_key VARCHAR(255) NOT NULL, + CONSTRAINT rollout_segment_id_namespace_segment UNIQUE (rollout_segment_id, namespace_key, segment_key), + FOREIGN KEY (rollout_segment_id) REFERENCES rollout_segments (id) ON DELETE CASCADE, + FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, `key`) ON DELETE CASCADE +); + +INSERT INTO rollout_segment_references (rollout_segment_id, namespace_key, segment_key) SELECT id AS rollout_segment_id, namespace_key, segment_key FROM rollout_segments; \ No newline at end of file diff --git a/config/migrations/postgres/11_segment_anding_tables.up.sql b/config/migrations/postgres/11_segment_anding_tables.up.sql new file mode 100644 index 0000000000..f974fb7fb7 --- /dev/null +++ b/config/migrations/postgres/11_segment_anding_tables.up.sql @@ -0,0 +1,21 @@ +-- Rules +CREATE TABLE IF NOT EXISTS rule_segments ( + rule_id VARCHAR(255) NOT NULL REFERENCES rules ON DELETE CASCADE, + namespace_key VARCHAR(255) NOT NULL, + segment_key VARCHAR(255) NOT NULL, + UNIQUE (rule_id, namespace_key, segment_key), + FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, key) ON DELETE CASCADE +); + +INSERT INTO rule_segments (rule_id, namespace_key, segment_key) SELECT id AS rule_id, namespace_key, segment_key FROM rules; + +-- Rollouts +CREATE TABLE IF NOT EXISTS rollout_segment_references ( + rollout_segment_id VARCHAR(255) NOT NULL REFERENCES rollout_segments ON DELETE CASCADE, + namespace_key VARCHAR(255) NOT NULL, + segment_key VARCHAR(255) NOT NULL, + UNIQUE (rollout_segment_id, namespace_key, segment_key), + FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, key) ON DELETE CASCADE +); + +INSERT INTO rollout_segment_references (rollout_segment_id, namespace_key, segment_key) SELECT id AS rollout_segment_id, namespace_key, segment_key FROM rollout_segments; \ No newline at end of file diff --git a/config/migrations/postgres/12_alter_rules_rollout_segments.up.sql b/config/migrations/postgres/12_alter_rules_rollout_segments.up.sql new file mode 100644 index 0000000000..2b107c9a15 --- /dev/null +++ b/config/migrations/postgres/12_alter_rules_rollout_segments.up.sql @@ -0,0 +1,15 @@ +-- Rules +ALTER TABLE rules DROP CONSTRAINT IF EXISTS rules_namespace_key_segment_key_fkey CASCADE; + +ALTER TABLE rules DROP COLUMN segment_key; + +ALTER TABLE rules ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0; + +-- Rollouts +ALTER TABLE rollout_segments DROP CONSTRAINT IF EXISTS rollout_segments_namespace_key_fkey CASCADE; +ALTER TABLE rollout_segments DROP CONSTRAINT IF EXISTS rollout_segments_namespace_key_segment_key_fkey CASCADE; + +ALTER TABLE rollout_segments DROP COLUMN segment_key; +ALTER TABLE rollout_segments DROP COLUMN namespace_key; + +ALTER TABLE rollout_segments ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/config/migrations/sqlite3/11_segment_anding_tables.up.sql b/config/migrations/sqlite3/11_segment_anding_tables.up.sql new file mode 100644 index 0000000000..a3411aa0f2 --- /dev/null +++ b/config/migrations/sqlite3/11_segment_anding_tables.up.sql @@ -0,0 +1,75 @@ +-- Rules +CREATE TABLE IF NOT EXISTS rules_temp ( + id VARCHAR(255) PRIMARY KEY UNIQUE NOT NULL, + flag_key VARCHAR(255) NOT NULL, + rank INTEGER DEFAULT 1 NOT NULL, + segment_operator INTEGER DEFAULT 0 NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + namespace_key VARCHAR(255) NOT NULL DEFAULT 'default' REFERENCES namespaces ON DELETE CASCADE, + FOREIGN KEY (namespace_key, flag_key) REFERENCES flags (namespace_key, key) ON DELETE CASCADE +); + +INSERT INTO rules_temp (id, flag_key, rank, created_at, updated_at, namespace_key) SELECT id, flag_key, rank, created_at, updated_at, namespace_key FROM rules; + +-- Copy data from distributions table to temporary distributions table since distributions depends on rules +CREATE TABLE distributions_temp ( + id VARCHAR(255) PRIMARY KEY UNIQUE NOT NULL, + rule_id VARCHAR(255) NOT NULL REFERENCES rules_temp ON DELETE CASCADE, + variant_id VARCHAR(255) NOT NULL REFERENCES variants ON DELETE CASCADE, + rollout float DEFAULT 0 NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL +); + +INSERT INTO distributions_temp (id, rule_id, variant_id, rollout, created_at, updated_at) + SELECT id, rule_id, variant_id, rollout, created_at, updated_at + FROM distributions; + +CREATE TABLE IF NOT EXISTS rule_segments ( + rule_id VARCHAR(255) NOT NULL REFERENCES rules_temp ON DELETE CASCADE, + namespace_key VARCHAR(255) NOT NULL, + segment_key VARCHAR(255) NOT NULL, + UNIQUE (rule_id, namespace_key, segment_key), + FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, key) ON DELETE CASCADE +); + +INSERT INTO rule_segments (rule_id, namespace_key, segment_key) SELECT id AS rule_id, namespace_key, segment_key FROM rules; + +-- Rollouts +CREATE TABLE IF NOT EXISTS rollout_segments_temp ( + id VARCHAR(255) PRIMARY KEY UNIQUE NOT NULL, + rollout_id VARCHAR(255) NOT NULL REFERENCES rollouts ON DELETE CASCADE, + value BOOLEAN DEFAULT FALSE NOT NULL, + segment_operator INTEGER DEFAULT 0 NOT NULL +); + +INSERT INTO rollout_segments_temp (id, rollout_id, value) SELECT id, rollout_id, value FROM rollout_segments; + +CREATE TABLE IF NOT EXISTS rollout_segment_references ( + rollout_segment_id VARCHAR(255) NOT NULL REFERENCES rollout_segments_temp ON DELETE CASCADE, + namespace_key VARCHAR(255) NOT NULL, + segment_key VARCHAR(255) NOT NULL, + UNIQUE (rollout_segment_id, namespace_key, segment_key), + FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, key) ON DELETE CASCADE +); + +INSERT INTO rollout_segment_references (rollout_segment_id, namespace_key, segment_key) SELECT id AS rollout_segment_id, namespace_key, segment_key FROM rollout_segments; + +-- Drop old rules table +DROP TABLE rules; + +-- Rename temporary rules table to rules +ALTER TABLE rules_temp RENAME TO rules; + +-- Drop old rollout_segments table +DROP TABLE rollout_segments; + +-- Rename temporary rollout_segments table to rollout_segments +ALTER TABLE rollout_segments_temp RENAME TO rollout_segments; + +-- Drop distributions table +DROP TABLE distributions; + +-- Rename distributions +ALTER TABLE distributions_temp RENAME TO distributions; \ No newline at end of file diff --git a/internal/cue/flipt.cue b/internal/cue/flipt.cue index 72e0199058..2995efcd8d 100644 --- a/internal/cue/flipt.cue +++ b/internal/cue/flipt.cue @@ -28,14 +28,19 @@ close({ } #Variant: { - key: string & =~"^.+$" - name: string & =~"^.+$" + key: string & =~"^.+$" + name: string & =~"^.+$" description?: string - attachment: {...} | *null + attachment: {...} | *null +} + +#RuleSegment: { + keys: [...string] + operator: "OR_SEGMENT_OPERATOR" | "AND_SEGMENT_OPERATOR" | *null } #Rule: { - segment: string & =~"^.+$" + segment: string & =~"^[-_,A-Za-z0-9]+$" | #RuleSegment rank?: int distributions: [...#Distribution] } @@ -45,10 +50,13 @@ close({ rollout: >=0 & <=100 } +#RolloutSegment: {key: string & =~"^[-_,A-Za-z0-9]+$"} | {keys: [...string]} + #Rollout: { segment: { - key: string - value: bool + #RolloutSegment + operator: "OR_SEGMENT_OPERATOR" | "AND_SEGMENT_OPERATOR" | *null + value: bool } } | { threshold: { diff --git a/internal/cue/testdata/valid_segments_v2.yaml b/internal/cue/testdata/valid_segments_v2.yaml new file mode 100644 index 0000000000..52dee318fa --- /dev/null +++ b/internal/cue/testdata/valid_segments_v2.yaml @@ -0,0 +1,55 @@ +namespace: default +flags: +- key: flipt + name: flipt + description: flipt + enabled: false + variants: + - key: flipt + name: flipt + - key: flipt + name: flipt + description: I'm a description. + rules: + - segment: + keys: + - internal-users + - all-users + operator: AND_SEGMENT_OPERATOR + distributions: + - variant: fromFlipt + rollout: 100 + - segment: all-users + distributions: + - variant: fromFlipt2 + rollout: 100 +- key: boolean + name: Boolean + description: Boolean flag + enabled: false + rollouts: + - description: enabled for internal users + segment: + keys: + - internal-users + - all-users + operator: AND_SEGMENT_OPERATOR + value: true + - description: enabled for 50% + threshold: + percentage: 50.0 + value: true +segments: +- key: all-users + name: All Users + description: All Users + match_type: ALL_MATCH_TYPE +- key: internal-users + name: Internal Users + description: All internal users at flipt. + constraints: + - type: STRING_COMPARISON_TYPE + property: organization + operator: eq + value: flipt + match_type: ALL_MATCH_TYPE diff --git a/internal/cue/validate_test.go b/internal/cue/validate_test.go index 49a2096c21..5d6b749722 100644 --- a/internal/cue/validate_test.go +++ b/internal/cue/validate_test.go @@ -36,6 +36,18 @@ func TestValidate_Latest_Success(t *testing.T) { assert.Empty(t, res.Errors) } +func TestValidate_Latest_Segments_V2(t *testing.T) { + b, err := os.ReadFile("testdata/valid_segments_v2.yaml") + require.NoError(t, err) + + v, err := NewFeaturesValidator() + require.NoError(t, err) + + res, err := v.Validate("testdata/valid_segments_v2.yaml", b) + assert.NoError(t, err) + assert.Empty(t, res.Errors) +} + func TestValidate_Failure(t *testing.T) { b, err := os.ReadFile("testdata/invalid.yaml") require.NoError(t, err) diff --git a/internal/ext/common.go b/internal/ext/common.go index 4399cecd45..3473c9b370 100644 --- a/internal/ext/common.go +++ b/internal/ext/common.go @@ -1,5 +1,9 @@ package ext +import ( + "errors" +) + type Document struct { Version string `yaml:"version,omitempty"` Namespace string `yaml:"namespace,omitempty"` @@ -26,7 +30,7 @@ type Variant struct { } type Rule struct { - SegmentKey string `yaml:"segment,omitempty"` + Segment *SegmentEmbed `yaml:"segment,omitempty"` Rank uint `yaml:"rank,omitempty"` Distributions []*Distribution `yaml:"distributions,omitempty"` } @@ -43,8 +47,10 @@ type Rollout struct { } type SegmentRule struct { - Key string `yaml:"key,omitempty"` - Value bool `yaml:"value,omitempty"` + Key string `yaml:"key,omitempty"` + Keys []string `yaml:"keys,omitempty"` + Operator string `yaml:"operator,omitempty"` + Value bool `yaml:"value,omitempty"` } type ThresholdRule struct { @@ -67,3 +73,60 @@ type Constraint struct { Value string `yaml:"value,omitempty"` Description string `yaml:"description,omitempty"` } + +type SegmentEmbed struct { + IsSegment `yaml:"-"` +} + +// MarshalYAML tries to type assert to either of the following types that implement +// IsSegment, and returns the marshaled value. +func (s *SegmentEmbed) MarshalYAML() (interface{}, error) { + switch t := s.IsSegment.(type) { + case SegmentKey: + return string(t), nil + case *Segments: + sk := &Segments{ + Keys: t.Keys, + SegmentOperator: t.SegmentOperator, + } + return sk, nil + } + + return nil, errors.New("failed to marshal to string or segmentKeys") +} + +// UnmarshalYAML attempts to unmarshal a string or `SegmentKeys`, and fails if it can not +// do so. +func (s *SegmentEmbed) UnmarshalYAML(unmarshal func(interface{}) error) error { + var sk SegmentKey + + if err := unmarshal(&sk); err == nil { + s.IsSegment = sk + return nil + } + + var sks *Segments + if err := unmarshal(&sks); err == nil { + s.IsSegment = sks + return nil + } + + return errors.New("failed to unmarshal to string or segmentKeys") +} + +// IsSegment is used to unify the two types of segments that can come in +// from the import. +type IsSegment interface { + IsSegment() +} + +type SegmentKey string + +func (s SegmentKey) IsSegment() {} + +type Segments struct { + Keys []string `yaml:"keys,omitempty"` + SegmentOperator string `yaml:"operator,omitempty"` +} + +func (s *Segments) IsSegment() {} diff --git a/internal/ext/exporter.go b/internal/ext/exporter.go index fd64ce0712..c448f3b168 100644 --- a/internal/ext/exporter.go +++ b/internal/ext/exporter.go @@ -16,7 +16,7 @@ const ( ) var ( - latestVersion = semver.Version{Major: 1, Minor: 1} + latestVersion = semver.Version{Major: 1, Minor: 2} supportedVersions = semver.Versions{ {Major: 1}, latestVersion, @@ -129,7 +129,23 @@ func (e *Exporter) Export(ctx context.Context, w io.Writer) error { rules := resp.Rules for _, r := range rules { - rule := &Rule{SegmentKey: r.SegmentKey} + rule := &Rule{} + + switch { + case r.SegmentKey != "": + rule.Segment = &SegmentEmbed{ + IsSegment: SegmentKey(r.SegmentKey), + } + case len(r.SegmentKeys) > 0: + rule.Segment = &SegmentEmbed{ + IsSegment: &Segments{ + Keys: r.SegmentKeys, + SegmentOperator: r.SegmentOperator.String(), + }, + } + default: + return fmt.Errorf("wrong format for rule segments") + } for _, d := range r.Distributions { rule.Distributions = append(rule.Distributions, &Distribution{ @@ -157,9 +173,18 @@ func (e *Exporter) Export(ctx context.Context, w io.Writer) error { switch rule := r.Rule.(type) { case *flipt.Rollout_Segment: rollout.Segment = &SegmentRule{ - Key: rule.Segment.SegmentKey, Value: rule.Segment.Value, } + + if rule.Segment.SegmentKey != "" { + rollout.Segment.Key = rule.Segment.SegmentKey + } else if len(rule.Segment.SegmentKeys) > 0 { + rollout.Segment.Keys = rule.Segment.SegmentKeys + } + + if rule.Segment.SegmentOperator == flipt.SegmentOperator_AND_SEGMENT_OPERATOR { + rollout.Segment.Operator = rule.Segment.SegmentOperator.String() + } case *flipt.Rollout_Threshold: rollout.Threshold = &ThresholdRule{ Percentage: rule.Threshold.Percentage, diff --git a/internal/ext/exporter_test.go b/internal/ext/exporter_test.go index ba89afe257..c2edc2e213 100644 --- a/internal/ext/exporter_test.go +++ b/internal/ext/exporter_test.go @@ -124,6 +124,12 @@ func TestExport(t *testing.T) { }, }, }, + { + Key: "segment2", + Name: "segment2", + Description: "description", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + }, }, rules: []*flipt.Rule{ { @@ -139,6 +145,12 @@ func TestExport(t *testing.T) { }, }, }, + { + Id: "2", + SegmentKeys: []string{"segment1", "segment2"}, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + Rank: 2, + }, }, rollouts: []*flipt.Rollout{ { diff --git a/internal/ext/importer.go b/internal/ext/importer.go index 3786788364..2598d18ca7 100644 --- a/internal/ext/importer.go +++ b/internal/ext/importer.go @@ -248,12 +248,21 @@ func (i *Importer) Import(ctx context.Context, r io.Reader) (err error) { rank = int32(idx) + 1 } - rule, err := i.creator.CreateRule(ctx, &flipt.CreateRuleRequest{ + fcr := &flipt.CreateRuleRequest{ FlagKey: f.Key, - SegmentKey: r.SegmentKey, Rank: rank, NamespaceKey: namespace, - }) + } + + switch s := r.Segment.IsSegment.(type) { + case SegmentKey: + fcr.SegmentKey = string(s) + case *Segments: + fcr.SegmentKeys = s.Keys + fcr.SegmentOperator = flipt.SegmentOperator(flipt.SegmentOperator_value[s.SegmentOperator]) + } + + rule, err := i.creator.CreateRule(ctx, fcr) if err != nil { return fmt.Errorf("creating rule: %w", err) @@ -309,11 +318,35 @@ func (i *Importer) Import(ctx context.Context, r io.Reader) (err error) { } if r.Segment != nil { + frs := &flipt.RolloutSegment{ + Value: r.Segment.Value, + SegmentKey: r.Segment.Key, + } + + if len(r.Segment.Keys) > 0 && r.Segment.Key != "" { + return fmt.Errorf("rollout %s/%s/%d cannot have both segment.keys and segment.key", + namespace, + f.Key, + idx, + ) + } + + // support explicitly setting only "keys" on rules from 1.2 + if len(r.Segment.Keys) > 0 { + if err := ensureFieldSupported("flag.rollouts[*].segment.keys", semver.Version{ + Major: 1, + Minor: 2, + }, v); err != nil { + return err + } + + frs.SegmentKeys = r.Segment.Keys + } + + frs.SegmentOperator = flipt.SegmentOperator(flipt.SegmentOperator_value[r.Segment.Operator]) + req.Rule = &flipt.CreateRolloutRequest_Segment{ - Segment: &flipt.RolloutSegment{ - SegmentKey: r.Segment.Key, - Value: r.Segment.Value, - }, + Segment: frs, } } else if r.Threshold != nil { req.Rule = &flipt.CreateRolloutRequest_Threshold{ diff --git a/internal/ext/importer_test.go b/internal/ext/importer_test.go index bf778ad3e2..54d697d30c 100644 --- a/internal/ext/importer_test.go +++ b/internal/ext/importer_test.go @@ -187,6 +187,11 @@ func TestImport(t *testing.T) { path: "testdata/import_implicit_rule_rank.yml", hasAttachment: true, }, + { + name: "import with multiple segments", + path: "testdata/import_rule_multiple_segments.yml", + hasAttachment: true, + }, } for _, tc := range tests { @@ -263,7 +268,12 @@ func TestImport(t *testing.T) { require.Len(t, creator.ruleReqs, 1) rule := creator.ruleReqs[0] - assert.Equal(t, "segment1", rule.SegmentKey) + if rule.SegmentKey != "" { + assert.Equal(t, "segment1", rule.SegmentKey) + } else { + assert.Len(t, rule.SegmentKeys, 1) + assert.Equal(t, "segment1", rule.SegmentKeys[0]) + } assert.Equal(t, int32(1), rule.Rank) require.Len(t, creator.distributionReqs, 1) diff --git a/internal/ext/testdata/export.yml b/internal/ext/testdata/export.yml index 51827f647f..c82cd0820a 100644 --- a/internal/ext/testdata/export.yml +++ b/internal/ext/testdata/export.yml @@ -1,4 +1,4 @@ -version: "1.1" +version: "1.2" namespace: default flags: - key: flag1 @@ -29,6 +29,11 @@ flags: distributions: - variant: variant1 rollout: 100 + - segment: + keys: + - segment1 + - segment2 + operator: AND_SEGMENT_OPERATOR - key: flag2 name: flag2 type: "BOOLEAN_FLAG_TYPE" @@ -59,3 +64,7 @@ segments: operator: neq value: buzz description: desc + - key: segment2 + name: segment2 + match_type: "ANY_MATCH_TYPE" + description: description diff --git a/internal/ext/testdata/import_rule_multiple_segments.yml b/internal/ext/testdata/import_rule_multiple_segments.yml new file mode 100644 index 0000000000..3b0bd60d3f --- /dev/null +++ b/internal/ext/testdata/import_rule_multiple_segments.yml @@ -0,0 +1,55 @@ +flags: + - key: flag1 + name: flag1 + type: "VARIANT_FLAG_TYPE" + description: description + enabled: true + variants: + - key: variant1 + name: variant1 + description: "variant description" + attachment: + pi: 3.141 + happy: true + name: Niels + answer: + everything: 42 + list: + - 1 + - 0 + - 2 + object: + currency: USD + value: 42.99 + rules: + - segment: + keys: + - segment1 + operator: OR_SEGMENT_OPERATOR + distributions: + - variant: variant1 + rollout: 100 + - key: flag2 + name: flag2 + type: "BOOLEAN_FLAG_TYPE" + description: a boolean flag + enabled: false + rollouts: + - description: enabled for internal users + segment: + key: internal_users + value: true + - description: enabled for 50% + threshold: + percentage: 50 + value: true +segments: + - key: segment1 + name: segment1 + match_type: "ANY_MATCH_TYPE" + description: description + constraints: + - type: STRING_COMPARISON_TYPE + property: fizz + operator: neq + value: buzz diff --git a/internal/server/evaluation/evaluation.go b/internal/server/evaluation/evaluation.go index e9d123e8ee..f7760e605e 100644 --- a/internal/server/evaluation/evaluation.go +++ b/internal/server/evaluation/evaluation.go @@ -77,12 +77,15 @@ func (s *Server) variant(ctx context.Context, flag *flipt.Flag, r *rpcevaluation ver := &rpcevaluation.VariantEvaluationResponse{ Match: resp.Match, - SegmentKeys: []string{resp.SegmentKey}, Reason: reason, VariantKey: resp.Value, VariantAttachment: resp.Attachment, } + if len(resp.SegmentKeys) > 0 { + ver.SegmentKeys = resp.SegmentKeys + } + return ver, nil } @@ -190,20 +193,43 @@ func (s *Server) boolean(ctx context.Context, flag *flipt.Flag, r *rpcevaluation return resp, nil } } else if rollout.Segment != nil { - matched, reason, err := matchConstraints(r.Context, rollout.Segment.Constraints, rollout.Segment.MatchType) - if err != nil { - return nil, err + + var ( + segmentMatches = 0 + segmentKeys = []string{} + ) + + for k, v := range rollout.Segment.Segments { + segmentKeys = append(segmentKeys, k) + matched, reason, err := matchConstraints(r.Context, v.Constraints, v.MatchType) + if err != nil { + return nil, err + } + + // if we don't match the segment, fall through to the next rollout. + if matched { + s.logger.Debug(reason) + segmentMatches++ + } } - // if we don't match the segment, fall through to the next rollout. - if !matched { - s.logger.Debug(reason) - continue + switch rollout.Segment.SegmentOperator { + case flipt.SegmentOperator_OR_SEGMENT_OPERATOR: + if segmentMatches < 1 { + s.logger.Debug("did not match ANY segments") + continue + } + case flipt.SegmentOperator_AND_SEGMENT_OPERATOR: + if len(rollout.Segment.Segments) != segmentMatches { + s.logger.Debug("did not match ALL segments") + continue + } } resp.Enabled = rollout.Segment.Value resp.Reason = rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON - s.logger.Debug("segment based matched", zap.Int("rank", int(rollout.Rank)), zap.String("segment", rollout.Segment.Key)) + + s.logger.Debug("segment based matched", zap.Int("rank", int(rollout.Rank)), zap.Strings("segments", segmentKeys)) return resp, nil } } @@ -211,6 +237,7 @@ func (s *Server) boolean(ctx context.Context, flag *flipt.Flag, r *rpcevaluation // If we have exhausted all rollouts and we still don't have a match, return flag enabled value. resp.Reason = rpcevaluation.EvaluationReason_DEFAULT_EVALUATION_REASON resp.Enabled = flag.Enabled + s.logger.Debug("default rollout matched", zap.Bool("enabled", flag.Enabled)) return resp, nil } diff --git a/internal/server/evaluation/evaluation_test.go b/internal/server/evaluation/evaluation_test.go index 1ce6142fbc..ba82e574d1 100644 --- a/internal/server/evaluation/evaluation_test.go +++ b/internal/server/evaluation/evaluation_test.go @@ -69,6 +69,7 @@ func TestVariant_NonVariantFlag(t *testing.T) { assert.EqualError(t, err, "flag type BOOLEAN_FLAG_TYPE invalid") } + func TestVariant_FlagDisabled(t *testing.T) { var ( flagKey = "test-flag" @@ -152,18 +153,22 @@ func TestVariant_Success(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, namespaceKey, flagKey).Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: flagKey, - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "hello", - Operator: flipt.OpEQ, - Value: "world", + ID: "1", + FlagKey: flagKey, + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "hello", + Operator: flipt.OpEQ, + Value: "world", + }, + }, }, }, }, @@ -394,15 +399,20 @@ func TestBoolean_PercentageRuleFallthrough_SegmentMatch(t *testing.T) { RolloutType: flipt.RolloutType_SEGMENT_ROLLOUT_TYPE, Rank: 2, Segment: &storage.RolloutSegment{ - Key: "test-segment", - MatchType: flipt.MatchType_ANY_MATCH_TYPE, - Value: true, - Constraints: []storage.EvaluationConstraint{ - { - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "hello", - Operator: flipt.OpEQ, - Value: "world", + Value: true, + SegmentOperator: flipt.SegmentOperator_OR_SEGMENT_OPERATOR, + Segments: map[string]*storage.EvaluationSegment{ + "test-segment": { + SegmentKey: "test-segment", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "hello", + Operator: flipt.OpEQ, + Value: "world", + }, + }, }, }, }, @@ -447,21 +457,26 @@ func TestBoolean_SegmentMatch_MultipleConstraints(t *testing.T) { RolloutType: flipt.RolloutType_SEGMENT_ROLLOUT_TYPE, Rank: 1, Segment: &storage.RolloutSegment{ - Key: "test-segment", - MatchType: flipt.MatchType_ANY_MATCH_TYPE, - Value: true, - Constraints: []storage.EvaluationConstraint{ - { - Type: flipt.ComparisonType_NUMBER_COMPARISON_TYPE, - Property: "pitimes100", - Operator: flipt.OpEQ, - Value: "314", - }, - { - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "hello", - Operator: flipt.OpEQ, - Value: "world", + Value: true, + SegmentOperator: flipt.SegmentOperator_OR_SEGMENT_OPERATOR, + Segments: map[string]*storage.EvaluationSegment{ + "test-segment": { + SegmentKey: "test-segment", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + Type: flipt.ComparisonType_NUMBER_COMPARISON_TYPE, + Property: "pitimes100", + Operator: flipt.OpEQ, + Value: "314", + }, + { + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "hello", + Operator: flipt.OpEQ, + Value: "world", + }, + }, }, }, }, @@ -483,6 +498,77 @@ func TestBoolean_SegmentMatch_MultipleConstraints(t *testing.T) { assert.Equal(t, rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON, res.Reason) } +func TestBoolean_SegmentMatch_MultipleSegments_WithAnd(t *testing.T) { + var ( + flagKey = "test-flag" + namespaceKey = "test-namespace" + store = &evaluationStoreMock{} + logger = zaptest.NewLogger(t) + s = New(logger, store) + ) + + store.On("GetFlag", mock.Anything, namespaceKey, flagKey).Return( + &flipt.Flag{ + NamespaceKey: "test-namespace", + Key: "test-flag", + Enabled: true, + Type: flipt.FlagType_BOOLEAN_FLAG_TYPE, + }, nil) + + store.On("GetEvaluationRollouts", mock.Anything, namespaceKey, flagKey).Return([]*storage.EvaluationRollout{ + { + NamespaceKey: namespaceKey, + RolloutType: flipt.RolloutType_SEGMENT_ROLLOUT_TYPE, + Rank: 1, + Segment: &storage.RolloutSegment{ + Value: true, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + Segments: map[string]*storage.EvaluationSegment{ + "test-segment": { + SegmentKey: "test-segment", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + Type: flipt.ComparisonType_NUMBER_COMPARISON_TYPE, + Property: "pitimes100", + Operator: flipt.OpEQ, + Value: "314", + }, + }, + }, + "another-segment": { + SegmentKey: "another-segment", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "hello", + Operator: flipt.OpEQ, + Value: "world", + }, + }, + }, + }, + }, + }, + }, nil) + + res, err := s.Boolean(context.TODO(), &rpcevaluation.EvaluationRequest{ + FlagKey: flagKey, + EntityId: "test-entity", + NamespaceKey: namespaceKey, + Context: map[string]string{ + "hello": "world", + "pitimes100": "314", + }, + }) + + require.NoError(t, err) + + assert.Equal(t, true, res.Enabled) + assert.Equal(t, rpcevaluation.EvaluationReason_MATCH_EVALUATION_REASON, res.Reason) +} + func TestBoolean_RulesOutOfOrder(t *testing.T) { var ( flagKey = "test-flag" @@ -515,15 +601,20 @@ func TestBoolean_RulesOutOfOrder(t *testing.T) { RolloutType: flipt.RolloutType_SEGMENT_ROLLOUT_TYPE, Rank: 0, Segment: &storage.RolloutSegment{ - Key: "test-segment", - MatchType: flipt.MatchType_ANY_MATCH_TYPE, - Value: true, - Constraints: []storage.EvaluationConstraint{ - { - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "hello", - Operator: flipt.OpEQ, - Value: "world", + Value: true, + SegmentOperator: flipt.SegmentOperator_OR_SEGMENT_OPERATOR, + Segments: map[string]*storage.EvaluationSegment{ + "test-segment": { + SegmentKey: "test-segment", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "hello", + Operator: flipt.OpEQ, + Value: "world", + }, + }, }, }, }, @@ -651,18 +742,22 @@ func TestBatch_Success(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, namespaceKey, variantFlagKey).Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: variantFlagKey, - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "hello", - Operator: flipt.OpEQ, - Value: "world", + ID: "1", + FlagKey: variantFlagKey, + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "hello", + Operator: flipt.OpEQ, + Value: "world", + }, + }, }, }, }, diff --git a/internal/server/evaluation/legacy_evaluator.go b/internal/server/evaluation/legacy_evaluator.go index b2af4ff7ca..53a66c9fe4 100644 --- a/internal/server/evaluation/legacy_evaluator.go +++ b/internal/server/evaluation/legacy_evaluator.go @@ -116,20 +116,44 @@ func (e *Evaluator) Evaluate(ctx context.Context, flag *flipt.Flag, r *flipt.Eva lastRank = rule.Rank - matched, reason, err := matchConstraints(r.Context, rule.Constraints, rule.SegmentMatchType) - if err != nil { - resp.Reason = flipt.EvaluationReason_ERROR_EVALUATION_REASON - return resp, err + segmentKeys := make([]string, 0, len(rule.Segments)) + segmentMatches := 0 + + for k, v := range rule.Segments { + matched, reason, err := matchConstraints(r.Context, v.Constraints, v.MatchType) + if err != nil { + resp.Reason = flipt.EvaluationReason_ERROR_EVALUATION_REASON + return resp, err + } + + if matched { + e.logger.Debug(reason) + segmentKeys = append(segmentKeys, k) + segmentMatches++ + } } - if !matched { - e.logger.Debug(reason) - continue + switch rule.SegmentOperator { + case flipt.SegmentOperator_OR_SEGMENT_OPERATOR: + if segmentMatches < 1 { + e.logger.Debug("did not match ANY segments") + continue + } + case flipt.SegmentOperator_AND_SEGMENT_OPERATOR: + if len(rule.Segments) != segmentMatches { + e.logger.Debug("did not match ALL segments") + continue + } } - // otherwise, this is our matching rule, determine the flag variant to return - // based on the distributions - resp.SegmentKey = rule.SegmentKey + // For legacy reasons of supporting SegmentKey. + // The old EvaluationResponse will return both the SegmentKey, and SegmentKeys. + // If there are multiple segmentKeys, the old EvaluationResponse will take the first value + // in the segmentKeys slice. + if len(segmentKeys) > 0 { + resp.SegmentKey = segmentKeys[0] + resp.SegmentKeys = segmentKeys + } distributions, err := e.store.GetEvaluationDistributions(ctx, rule.ID) if err != nil { diff --git a/internal/server/evaluation/legacy_evaluator_test.go b/internal/server/evaluation/legacy_evaluator_test.go index 902fb93d0e..0478304089 100644 --- a/internal/server/evaluation/legacy_evaluator_test.go +++ b/internal/server/evaluation/legacy_evaluator_test.go @@ -849,34 +849,42 @@ func TestEvaluator_RulesOutOfOrder(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 1, - Constraints: []storage.EvaluationConstraint{ - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "1", + FlagKey: "foo", + Rank: 1, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, }, }, { - ID: "2", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "2", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, }, }, @@ -906,18 +914,22 @@ func TestEvaluator_ErrorParsingNumber(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 1, - Constraints: []storage.EvaluationConstraint{ - { - ID: "2", - Type: flipt.ComparisonType_NUMBER_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "boz", + ID: "1", + FlagKey: "foo", + Rank: 1, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_NUMBER_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "boz", + }, + }, }, }, }, @@ -946,18 +958,22 @@ func TestEvaluator_ErrorParsingDateTime(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 1, - Constraints: []storage.EvaluationConstraint{ - { - ID: "2", - Type: flipt.ComparisonType_DATETIME_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "boz", + ID: "1", + FlagKey: "foo", + Rank: 1, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_DATETIME_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "boz", + }, + }, }, }, }, @@ -987,18 +1003,22 @@ func TestEvaluator_ErrorGettingDistributions(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, }, }, @@ -1030,18 +1050,22 @@ func TestEvaluator_MatchAll_NoVariants_NoDistributions(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, }, }, @@ -1104,7 +1128,7 @@ func TestEvaluator_MatchAll_NoVariants_NoDistributions(t *testing.T) { } } -func TestEvaluator_DistributionNotMatched(t *testing.T) { +func TestEvaluator_MatchAll_MultipleSegments(t *testing.T) { var ( store = &evaluationStoreMock{} logger = zaptest.NewLogger(t) @@ -1114,26 +1138,136 @@ func TestEvaluator_DistributionNotMatched(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "1", + FlagKey: "foo", + Rank: 0, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, - // constraint: admin (bool) == true - { - ID: "3", - Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, - Property: "admin", - Operator: flipt.OpTrue, + "foo": { + SegmentKey: "foo", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "company", + Operator: flipt.OpEQ, + Value: "flipt", + }, + }, + }, + }, + }, + }, nil) + + store.On("GetEvaluationDistributions", mock.Anything, "1").Return([]*storage.EvaluationDistribution{}, nil) + + tests := []struct { + name string + req *flipt.EvaluationRequest + wantMatch bool + }{ + { + name: "match string value", + req: &flipt.EvaluationRequest{ + FlagKey: "foo", + EntityId: "1", + Context: map[string]string{ + "bar": "baz", + "company": "flipt", + }, + }, + wantMatch: true, + }, + { + name: "no match string value", + req: &flipt.EvaluationRequest{ + FlagKey: "foo", + EntityId: "1", + Context: map[string]string{ + "bar": "boz", + }, + }, + }, + } + + for _, tt := range tests { + var ( + req = tt.req + wantMatch = tt.wantMatch + ) + + t.Run(tt.name, func(t *testing.T) { + resp, err := s.Evaluate(context.TODO(), enabledFlag, req) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, "foo", resp.FlagKey) + assert.Equal(t, req.Context, resp.RequestContext) + + if !wantMatch { + assert.False(t, resp.Match) + assert.Empty(t, resp.SegmentKey, "segment key should be empty") + return + } + + assert.True(t, resp.Match) + + assert.Len(t, resp.SegmentKeys, 2) + assert.Contains(t, resp.SegmentKeys, "bar") + assert.Contains(t, resp.SegmentKeys, "foo") + assert.Empty(t, resp.Value) + assert.Equal(t, flipt.EvaluationReason_MATCH_EVALUATION_REASON, resp.Reason) + }) + } +} + +func TestEvaluator_DistributionNotMatched(t *testing.T) { + var ( + store = &evaluationStoreMock{} + logger = zaptest.NewLogger(t) + s = NewEvaluator(logger, store) + ) + + store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( + []*storage.EvaluationRule{ + { + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + // constraint: admin (bool) == true + { + ID: "3", + Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, + Property: "admin", + Operator: flipt.OpTrue, + }, + }, }, }, }, @@ -1177,26 +1311,30 @@ func TestEvaluator_MatchAll_SingleVariantDistribution(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", - }, - // constraint: admin (bool) == true - { - ID: "3", - Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, - Property: "admin", - Operator: flipt.OpTrue, + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + // constraint: admin (bool) == true + { + ID: "3", + Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, + Property: "admin", + Operator: flipt.OpTrue, + }, + }, }, }, }, @@ -1302,19 +1440,23 @@ func TestEvaluator_MatchAll_RolloutDistribution(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, }, }, @@ -1418,27 +1560,35 @@ func TestEvaluator_MatchAll_RolloutDistribution_MultiRule(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "subscribers", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: premium_user (bool) == true - { - ID: "2", - Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, - Property: "premium_user", - Operator: flipt.OpTrue, + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "subscribers": { + SegmentKey: "subscribers", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: premium_user (bool) == true + { + ID: "2", + Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, + Property: "premium_user", + Operator: flipt.OpTrue, + }, + }, }, }, }, { - ID: "2", - FlagKey: "foo", - SegmentKey: "all_users", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 1, + ID: "2", + FlagKey: "foo", + Rank: 1, + Segments: map[string]*storage.EvaluationSegment{ + "all_users": { + SegmentKey: "all_users", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + }, + }, }, }, nil) @@ -1488,11 +1638,15 @@ func TestEvaluator_MatchAll_NoConstraints(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + }, + }, }, }, nil) @@ -1594,18 +1748,22 @@ func TestEvaluator_MatchAny_NoVariants_NoDistributions(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ANY_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, }, }, @@ -1678,26 +1836,30 @@ func TestEvaluator_MatchAny_SingleVariantDistribution(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ANY_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", - }, - // constraint: admin (bool) == true - { - ID: "3", - Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, - Property: "admin", - Operator: flipt.OpTrue, + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + // constraint: admin (bool) == true + { + ID: "3", + Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, + Property: "admin", + Operator: flipt.OpTrue, + }, + }, }, }, }, @@ -1835,19 +1997,23 @@ func TestEvaluator_MatchAny_RolloutDistribution(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ANY_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, }, }, @@ -1951,27 +2117,44 @@ func TestEvaluator_MatchAny_RolloutDistribution_MultiRule(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "subscribers", - SegmentMatchType: flipt.MatchType_ANY_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: premium_user (bool) == true - { - ID: "2", - Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, - Property: "premium_user", - Operator: flipt.OpTrue, + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "subscribers": { + SegmentKey: "subscribers", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: premium_user (bool) == true + { + ID: "2", + Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, + Property: "premium_user", + Operator: flipt.OpTrue, + }, + }, }, }, }, { - ID: "2", - FlagKey: "foo", - SegmentKey: "all_users", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 1, + ID: "2", + FlagKey: "foo", + Rank: 1, + Segments: map[string]*storage.EvaluationSegment{ + "all_users": { + SegmentKey: "all_users", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: premium_user (bool) == true + { + ID: "2", + Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, + Property: "premium_user", + Operator: flipt.OpTrue, + }, + }, + }, + }, }, }, nil) @@ -2021,11 +2204,15 @@ func TestEvaluator_MatchAny_NoConstraints(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ANY_MATCH_TYPE, - Rank: 0, + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + }, + }, }, }, nil) @@ -2127,19 +2314,23 @@ func TestEvaluator_FirstRolloutRuleIsZero(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, }, }, @@ -2222,19 +2413,23 @@ func TestEvaluator_MultipleZeroRolloutDistributions(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + }, }, }, }, diff --git a/internal/server/middleware/grpc/middleware_test.go b/internal/server/middleware/grpc/middleware_test.go index d066ef6f3c..db5651cd50 100644 --- a/internal/server/middleware/grpc/middleware_test.go +++ b/internal/server/middleware/grpc/middleware_test.go @@ -358,7 +358,9 @@ func TestEvaluationUnaryInterceptor_BatchEvaluation(t *testing.T) { // check that the requestID was propagated assert.NotEmpty(t, resp.RequestId) assert.Equal(t, "bar", resp.RequestId) - assert.NotZero(t, resp.RequestDurationMillis) + + // TODO(yquansah): flakey assertion + // assert.NotZero(t, resp.RequestDurationMillis) } func TestCacheUnaryInterceptor_GetFlag(t *testing.T) { @@ -639,26 +641,30 @@ func TestCacheUnaryInterceptor_Evaluate(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", - }, - // constraint: admin (bool) == true - { - ID: "3", - Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, - Property: "admin", - Operator: flipt.OpTrue, + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + // constraint: admin (bool) == true + { + ID: "3", + Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, + Property: "admin", + Operator: flipt.OpTrue, + }, + }, }, }, }, @@ -791,26 +797,30 @@ func TestCacheUnaryInterceptor_Evaluation_Variant(t *testing.T) { store.On("GetEvaluationRules", mock.Anything, mock.Anything, "foo").Return( []*storage.EvaluationRule{ { - ID: "1", - FlagKey: "foo", - SegmentKey: "bar", - SegmentMatchType: flipt.MatchType_ALL_MATCH_TYPE, - Rank: 0, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", - }, - // constraint: admin (bool) == true - { - ID: "3", - Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, - Property: "admin", - Operator: flipt.OpTrue, + ID: "1", + FlagKey: "foo", + Rank: 0, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + // constraint: admin (bool) == true + { + ID: "3", + Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, + Property: "admin", + Operator: flipt.OpTrue, + }, + }, }, }, }, @@ -943,23 +953,27 @@ func TestCacheUnaryInterceptor_Evaluation_Boolean(t *testing.T) { { RolloutType: flipt.RolloutType_SEGMENT_ROLLOUT_TYPE, Segment: &storage.RolloutSegment{ - Key: "bar", - MatchType: flipt.MatchType_ALL_MATCH_TYPE, - Constraints: []storage.EvaluationConstraint{ - // constraint: bar (string) == baz - { - ID: "2", - Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, - Property: "bar", - Operator: flipt.OpEQ, - Value: "baz", - }, - // constraint: admin (bool) == true - { - ID: "3", - Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, - Property: "admin", - Operator: flipt.OpTrue, + Segments: map[string]*storage.EvaluationSegment{ + "bar": { + SegmentKey: "bar", + MatchType: flipt.MatchType_ALL_MATCH_TYPE, + Constraints: []storage.EvaluationConstraint{ + // constraint: bar (string) == baz + { + ID: "2", + Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, + Property: "bar", + Operator: flipt.OpEQ, + Value: "baz", + }, + // constraint: admin (bool) == true + { + ID: "3", + Type: flipt.ComparisonType_BOOLEAN_COMPARISON_TYPE, + Property: "admin", + Operator: flipt.OpTrue, + }, + }, }, }, Value: true, diff --git a/internal/server/rollout_test.go b/internal/server/rollout_test.go index 511439edcd..fe8705d976 100644 --- a/internal/server/rollout_test.go +++ b/internal/server/rollout_test.go @@ -109,8 +109,35 @@ func TestCreateRollout(t *testing.T) { Rank: 1, Rule: &flipt.Rollout_Segment{ Segment: &flipt.RolloutSegment{ - SegmentKey: "segmentKey", - Value: true, + SegmentOperator: flipt.SegmentOperator_OR_SEGMENT_OPERATOR, + SegmentKey: "segmentKey", + Value: true, + }, + }, + }, + }, + { + name: "segments", + req: &flipt.CreateRolloutRequest{ + FlagKey: "flagKey", + Rank: 1, + Rule: &flipt.CreateRolloutRequest_Segment{ + Segment: &flipt.RolloutSegment{ + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + SegmentKeys: []string{"segmentKey1", "segmentKey2"}, + Value: true, + }, + }, + }, + resp: &flipt.Rollout{ + Id: "1", + FlagKey: "flagKey", + Rank: 1, + Rule: &flipt.Rollout_Segment{ + Segment: &flipt.RolloutSegment{ + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + SegmentKeys: []string{"segmentKey1", "segmentKey2"}, + Value: true, }, }, }, diff --git a/internal/server/rule_test.go b/internal/server/rule_test.go index 3033a64fe7..0bd51e08a4 100644 --- a/internal/server/rule_test.go +++ b/internal/server/rule_test.go @@ -149,6 +149,36 @@ func TestCreateRule(t *testing.T) { assert.NotNil(t, got) } +func TestCreateRule_MultipleSegments(t *testing.T) { + var ( + store = &storeMock{} + logger = zaptest.NewLogger(t) + s = &Server{ + logger: logger, + store: store, + } + req = &flipt.CreateRuleRequest{ + FlagKey: "flagKey", + SegmentKeys: []string{"segmentKey1", "segmentKey2"}, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + Rank: 1, + } + ) + + store.On("CreateRule", mock.Anything, req).Return(&flipt.Rule{ + Id: "1", + FlagKey: req.FlagKey, + SegmentKeys: req.SegmentKeys, + SegmentOperator: req.SegmentOperator, + Rank: req.Rank, + }, nil) + + got, err := s.CreateRule(context.TODO(), req) + require.NoError(t, err) + + assert.NotNil(t, got) +} + func TestUpdateRule(t *testing.T) { var ( store = &storeMock{} diff --git a/internal/storage/fs/snapshot.go b/internal/storage/fs/snapshot.go index f0fc733e2d..41ff2d401b 100644 --- a/internal/storage/fs/snapshot.go +++ b/internal/storage/fs/snapshot.go @@ -296,7 +296,6 @@ func (ss *storeSnapshot) addDoc(doc *ext.Document) error { NamespaceKey: doc.Namespace, Id: uuid.Must(uuid.NewV4()).String(), FlagKey: f.Key, - SegmentKey: r.SegmentKey, Rank: rank, CreatedAt: ss.now, UpdatedAt: ss.now, @@ -307,25 +306,58 @@ func (ss *storeSnapshot) addDoc(doc *ext.Document) error { FlagKey: f.Key, ID: rule.Id, Rank: rank, - SegmentKey: rule.SegmentKey, } - segment := ns.segments[rule.SegmentKey] - if segment == nil { - return errs.ErrNotFoundf("segment %q in rule %d", rule.SegmentKey, rank) + switch s := r.Segment.IsSegment.(type) { + case ext.SegmentKey: + rule.SegmentKey = string(s) + case *ext.Segments: + rule.SegmentKeys = s.Keys + segmentOperator := flipt.SegmentOperator_value[s.SegmentOperator] + + rule.SegmentOperator = flipt.SegmentOperator(segmentOperator) } - evalRule.SegmentMatchType = segment.MatchType + var ( + segmentKeys = []string{} + segments = make(map[string]*storage.EvaluationSegment) + ) - for _, constraint := range segment.Constraints { - evalRule.Constraints = append(evalRule.Constraints, storage.EvaluationConstraint{ - Operator: constraint.Operator, - Property: constraint.Property, - Type: constraint.Type, - Value: constraint.Value, - }) + if rule.SegmentKey != "" { + segmentKeys = append(segmentKeys, rule.SegmentKey) + } else if len(rule.SegmentKeys) > 0 { + segmentKeys = append(segmentKeys, rule.SegmentKeys...) + } + + for _, segmentKey := range segmentKeys { + segment := ns.segments[segmentKey] + if segment == nil { + return errs.ErrNotFoundf("segment %q in rule %d", segmentKey, rank) + } + + evc := make([]storage.EvaluationConstraint, 0, len(segment.Constraints)) + for _, constraint := range segment.Constraints { + evc = append(evc, storage.EvaluationConstraint{ + Operator: constraint.Operator, + Property: constraint.Property, + Type: constraint.Type, + Value: constraint.Value, + }) + } + + segments[segmentKey] = &storage.EvaluationSegment{ + SegmentKey: segmentKey, + MatchType: segment.MatchType, + Constraints: evc, + } } + if rule.SegmentOperator == flipt.SegmentOperator_AND_SEGMENT_OPERATOR { + evalRule.SegmentOperator = flipt.SegmentOperator_AND_SEGMENT_OPERATOR + } + + evalRule.Segments = segments + evalRules = append(evalRules, evalRule) for _, d := range r.Distributions { @@ -390,35 +422,64 @@ func (ss *storeSnapshot) addDoc(doc *ext.Document) error { }, } } else if rollout.Segment != nil { - segment, ok := ns.segments[rollout.Segment.Key] - if !ok { - return errs.ErrNotFoundf("segment %q not found", rollout.Segment.Key) + var ( + segmentKeys = []string{} + segments = make(map[string]*storage.EvaluationSegment) + ) + + if rollout.Segment.Key != "" { + segmentKeys = append(segmentKeys, rollout.Segment.Key) + } else if len(rollout.Segment.Keys) > 0 { + segmentKeys = append(segmentKeys, rollout.Segment.Keys...) + } + + for _, segmentKey := range segmentKeys { + segment, ok := ns.segments[segmentKey] + if !ok { + return errs.ErrNotFoundf("segment %q not found", rollout.Segment.Key) + } + + constraints := make([]storage.EvaluationConstraint, 0, len(segment.Constraints)) + for _, c := range segment.Constraints { + constraints = append(constraints, storage.EvaluationConstraint{ + Operator: c.Operator, + Property: c.Property, + Type: c.Type, + Value: c.Value, + }) + } + + segments[segmentKey] = &storage.EvaluationSegment{ + SegmentKey: segmentKey, + MatchType: segment.MatchType, + Constraints: constraints, + } } + segmentOperator := flipt.SegmentOperator_value[rollout.Segment.Operator] + s.Segment = &storage.RolloutSegment{ - Key: rollout.Segment.Key, - Value: rollout.Segment.Value, + Segments: segments, + SegmentOperator: flipt.SegmentOperator(segmentOperator), + Value: rollout.Segment.Value, } + s.RolloutType = flipt.RolloutType_SEGMENT_ROLLOUT_TYPE - constraints := make([]storage.EvaluationConstraint, 0, len(segment.Constraints)) - for _, c := range segment.Constraints { - constraints = append(constraints, storage.EvaluationConstraint{ - Operator: c.Operator, - Property: c.Property, - Type: c.Type, - Value: c.Value, - }) + frs := &flipt.RolloutSegment{ + Value: rollout.Segment.Value, + SegmentOperator: flipt.SegmentOperator(segmentOperator), } - s.Segment.Constraints = constraints + if len(segmentKeys) == 1 { + frs.SegmentKey = segmentKeys[0] + } else { + frs.SegmentKeys = segmentKeys + } flagRollout.Type = s.RolloutType flagRollout.Rule = &flipt.Rollout_Segment{ - Segment: &flipt.RolloutSegment{ - SegmentKey: rollout.Segment.Key, - Value: rollout.Segment.Value, - }, + Segment: frs, } } diff --git a/internal/storage/fs/snapshot_test.go b/internal/storage/fs/snapshot_test.go index f753759ef3..2164cec0dc 100644 --- a/internal/storage/fs/snapshot_test.go +++ b/internal/storage/fs/snapshot_test.go @@ -437,7 +437,7 @@ func (fis *FSIndexSuite) TestGetEvaluationRollouts() { assert.Equal(t, int32(1), rollouts[0].Rank) require.NotNil(t, rollouts[0].Segment) - assert.Equal(t, "segment1", rollouts[0].Segment.Key) + assert.Contains(t, rollouts[0].Segment.Segments, "segment1") assert.True(t, rollouts[0].Segment.Value, "segment value should be true") assert.Equal(t, tc.namespace, rollouts[1].NamespaceKey) @@ -517,11 +517,12 @@ func (fis *FSIndexSuite) TestGetEvaluationRules() { assert.Equal(t, tc.namespace, rules[0].NamespaceKey) assert.Equal(t, tc.flagKey, rules[0].FlagKey) assert.Equal(t, int32(1), rules[0].Rank) - assert.Equal(t, "segment1", rules[0].SegmentKey) + assert.Contains(t, rules[0].Segments, "segment1") for i := 0; i < len(tc.constraints); i++ { fc := tc.constraints[i] - c := rules[0].Constraints[i] + c := rules[0].Segments[fc.SegmentKey].Constraints[i] + assert.Equal(t, fc.Type, c.Type) assert.Equal(t, fc.Property, c.Property) assert.Equal(t, fc.Operator, c.Operator) @@ -1342,7 +1343,7 @@ func (fis *FSWithoutIndexSuite) TestGetEvaluationRollouts() { assert.Equal(t, int32(1), rollouts[0].Rank) require.NotNil(t, rollouts[0].Segment) - assert.Equal(t, "segment1", rollouts[0].Segment.Key) + assert.Contains(t, rollouts[0].Segment.Segments, "segment1") assert.True(t, rollouts[0].Segment.Value, "segment value should be true") assert.Equal(t, tc.namespace, rollouts[1].NamespaceKey) @@ -1445,11 +1446,12 @@ func (fis *FSWithoutIndexSuite) TestGetEvaluationRules() { assert.Equal(t, tc.namespace, rules[0].NamespaceKey) assert.Equal(t, tc.flagKey, rules[0].FlagKey) assert.Equal(t, int32(1), rules[0].Rank) - assert.Equal(t, "segment1", rules[0].SegmentKey) + assert.Contains(t, rules[0].Segments, "segment1") for i := 0; i < len(tc.constraints); i++ { fc := tc.constraints[i] - c := rules[0].Constraints[i] + c := rules[0].Segments[fc.SegmentKey].Constraints[i] + assert.Equal(t, fc.Type, c.Type) assert.Equal(t, fc.Property, c.Property) assert.Equal(t, fc.Operator, c.Operator) diff --git a/internal/storage/sql/common/evaluation.go b/internal/storage/sql/common/evaluation.go index 457312b317..d3a92f2561 100644 --- a/internal/storage/sql/common/evaluation.go +++ b/internal/storage/sql/common/evaluation.go @@ -3,25 +3,75 @@ package common import ( "context" "database/sql" + "sort" sq "github.com/Masterminds/squirrel" "go.flipt.io/flipt/internal/storage" flipt "go.flipt.io/flipt/rpc/flipt" ) -func (s *Store) GetEvaluationRules(ctx context.Context, namespaceKey, flagKey string) ([]*storage.EvaluationRule, error) { +func (s *Store) GetEvaluationRules(ctx context.Context, namespaceKey, flagKey string) (_ []*storage.EvaluationRule, err error) { if namespaceKey == "" { namespaceKey = storage.DefaultNamespace } - // get all rules for flag with their constraints if any - rows, err := s.builder.Select("r.id, r.namespace_key, r.flag_key, r.segment_key, s.match_type, r.\"rank\", c.id, c.type, c.property, c.operator, c.value"). - From("rules r"). - Join("segments s ON (r.segment_key = s.\"key\" AND r.namespace_key = s.namespace_key)"). - LeftJoin("constraints c ON (s.\"key\" = c.segment_key AND s.namespace_key = c.namespace_key)"). - Where(sq.And{sq.Eq{"r.flag_key": flagKey}, sq.Eq{"r.namespace_key": namespaceKey}}). - OrderBy("r.\"rank\" ASC"). - GroupBy("r.id, c.id, s.match_type"). + ruleMetaRows, err := s.builder. + Select("id, \"rank\", segment_operator"). + From("rules"). + Where(sq.And{sq.Eq{"flag_key": flagKey}, sq.Eq{"namespace_key": namespaceKey}}). + QueryContext(ctx) + if err != nil { + return nil, err + } + + defer func() { + if cerr := ruleMetaRows.Close(); cerr != nil && err == nil { + err = cerr + } + }() + + type RuleMeta struct { + ID string + Rank int32 + SegmentOperator flipt.SegmentOperator + } + + var rmMap = make(map[string]*RuleMeta) + + ruleIDs := make([]string, 0) + for ruleMetaRows.Next() { + var rm RuleMeta + + if err := ruleMetaRows.Scan(&rm.ID, &rm.Rank, &rm.SegmentOperator); err != nil { + return nil, err + } + + rmMap[rm.ID] = &rm + ruleIDs = append(ruleIDs, rm.ID) + } + + if err := ruleMetaRows.Err(); err != nil { + return nil, err + } + + if err := ruleMetaRows.Close(); err != nil { + return nil, err + } + + rows, err := s.builder.Select(` + rs.rule_id, + rs.segment_key, + s.match_type AS segment_match_type, + c.id AS constraint_id, + c."type" AS constraint_type, + c.property AS constraint_property, + c.operator AS constraint_operator, + c.value AS constraint_value + `). + From("rule_segments AS rs"). + Join(`segments AS s ON rs.segment_key = s."key"`). + LeftJoin(`constraints AS c ON (s."key" = c.segment_key AND s.namespace_key = c.namespace_key)`). + Where(sq.Eq{"rs.rule_id": ruleIDs}). QueryContext(ctx) if err != nil { return nil, err @@ -40,17 +90,22 @@ func (s *Store) GetEvaluationRules(ctx context.Context, namespaceKey, flagKey st for rows.Next() { var ( - tempRule storage.EvaluationRule + intermediateStorageRule struct { + ID string + NamespaceKey string + FlagKey string + SegmentKey string + SegmentMatchType flipt.MatchType + SegmentOperator flipt.SegmentOperator + Rank int32 + } optionalConstraint optionalConstraint ) if err := rows.Scan( - &tempRule.ID, - &tempRule.NamespaceKey, - &tempRule.FlagKey, - &tempRule.SegmentKey, - &tempRule.SegmentMatchType, - &tempRule.Rank, + &intermediateStorageRule.ID, + &intermediateStorageRule.SegmentKey, + &intermediateStorageRule.SegmentMatchType, &optionalConstraint.Id, &optionalConstraint.Type, &optionalConstraint.Property, @@ -59,45 +114,82 @@ func (s *Store) GetEvaluationRules(ctx context.Context, namespaceKey, flagKey st return rules, err } - if existingRule, ok := uniqueRules[tempRule.ID]; ok { - // current rule we know about + rm := rmMap[intermediateStorageRule.ID] + + intermediateStorageRule.FlagKey = flagKey + intermediateStorageRule.NamespaceKey = namespaceKey + intermediateStorageRule.Rank = rm.Rank + intermediateStorageRule.SegmentOperator = rm.SegmentOperator + + if existingRule, ok := uniqueRules[intermediateStorageRule.ID]; ok { + var constraint *storage.EvaluationConstraint if optionalConstraint.Id.Valid { - constraint := storage.EvaluationConstraint{ + constraint = &storage.EvaluationConstraint{ ID: optionalConstraint.Id.String, Type: flipt.ComparisonType(optionalConstraint.Type.Int32), Property: optionalConstraint.Property.String, Operator: optionalConstraint.Operator.String, Value: optionalConstraint.Value.String, } - existingRule.Constraints = append(existingRule.Constraints, constraint) + } + + segment, ok := existingRule.Segments[intermediateStorageRule.SegmentKey] + if !ok { + ses := &storage.EvaluationSegment{ + SegmentKey: intermediateStorageRule.SegmentKey, + MatchType: intermediateStorageRule.SegmentMatchType, + } + + if constraint != nil { + ses.Constraints = []storage.EvaluationConstraint{*constraint} + } + + existingRule.Segments[intermediateStorageRule.SegmentKey] = ses + } else if constraint != nil { + segment.Constraints = append(segment.Constraints, *constraint) } } else { // haven't seen this rule before newRule := &storage.EvaluationRule{ - ID: tempRule.ID, - NamespaceKey: tempRule.NamespaceKey, - FlagKey: tempRule.FlagKey, - SegmentKey: tempRule.SegmentKey, - SegmentMatchType: tempRule.SegmentMatchType, - Rank: tempRule.Rank, + ID: intermediateStorageRule.ID, + NamespaceKey: intermediateStorageRule.NamespaceKey, + FlagKey: intermediateStorageRule.FlagKey, + Rank: intermediateStorageRule.Rank, + SegmentOperator: intermediateStorageRule.SegmentOperator, + Segments: make(map[string]*storage.EvaluationSegment), } + var constraint *storage.EvaluationConstraint if optionalConstraint.Id.Valid { - constraint := storage.EvaluationConstraint{ + constraint = &storage.EvaluationConstraint{ ID: optionalConstraint.Id.String, Type: flipt.ComparisonType(optionalConstraint.Type.Int32), Property: optionalConstraint.Property.String, Operator: optionalConstraint.Operator.String, Value: optionalConstraint.Value.String, } - newRule.Constraints = append(newRule.Constraints, constraint) } + ses := &storage.EvaluationSegment{ + SegmentKey: intermediateStorageRule.SegmentKey, + MatchType: intermediateStorageRule.SegmentMatchType, + } + + if constraint != nil { + ses.Constraints = []storage.EvaluationConstraint{*constraint} + } + + newRule.Segments[intermediateStorageRule.SegmentKey] = ses + uniqueRules[newRule.ID] = newRule rules = append(rules, newRule) } } + sort.Slice(rules, func(i, j int) bool { + return rules[i].Rank < rules[j].Rank + }) + if err := rows.Err(); err != nil { return rules, err } @@ -109,7 +201,7 @@ func (s *Store) GetEvaluationRules(ctx context.Context, namespaceKey, flagKey st return rules, nil } -func (s *Store) GetEvaluationDistributions(ctx context.Context, ruleID string) ([]*storage.EvaluationDistribution, error) { +func (s *Store) GetEvaluationDistributions(ctx context.Context, ruleID string) (_ []*storage.EvaluationDistribution, err error) { rows, err := s.builder.Select("d.id, d.rule_id, d.variant_id, d.rollout, v.\"key\", v.attachment"). From("distributions d"). Join("variants v ON (d.variant_id = v.id)"). @@ -162,7 +254,7 @@ func (s *Store) GetEvaluationDistributions(ctx context.Context, ruleID string) ( return distributions, nil } -func (s *Store) GetEvaluationRollouts(ctx context.Context, namespaceKey, flagKey string) ([]*storage.EvaluationRollout, error) { +func (s *Store) GetEvaluationRollouts(ctx context.Context, namespaceKey, flagKey string) (_ []*storage.EvaluationRollout, err error) { if namespaceKey == "" { namespaceKey = storage.DefaultNamespace } @@ -176,27 +268,30 @@ func (s *Store) GetEvaluationRollouts(ctx context.Context, namespaceKey, flagKey rt.value, rss.segment_key, rss.rollout_segment_value, + rss.segment_operator, rss.match_type, rss.constraint_type, rss.constraint_property, rss.constraint_operator, rss.constraint_value `). - From("rollouts r"). - LeftJoin("rollout_thresholds rt ON (r.id = rt.rollout_id)"). + From("rollouts AS r"). + LeftJoin("rollout_thresholds AS rt ON (r.id = rt.rollout_id)"). LeftJoin(`( SELECT rs.rollout_id, - rs.segment_key, + rsr.segment_key, s.match_type, rs.value AS rollout_segment_value, + rs.segment_operator AS segment_operator, c."type" AS constraint_type, c.property AS constraint_property, c.operator AS constraint_operator, c.value AS constraint_value - FROM rollout_segments rs - JOIN segments s ON (rs.segment_key = s."key") - LEFT JOIN constraints c ON (rs.segment_key = c.segment_key) + FROM rollout_segments AS rs + JOIN rollout_segment_references AS rsr ON (rs.id = rsr.rollout_segment_id) + JOIN segments AS s ON (rsr.segment_key = s."key") + LEFT JOIN constraints AS c ON (rsr.segment_key = c.segment_key) ) rss ON (r.id = rss.rollout_id) `). Where(sq.And{sq.Eq{"r.namespace_key": namespaceKey}, sq.Eq{"r.flag_key": flagKey}}). @@ -225,6 +320,7 @@ func (s *Store) GetEvaluationRollouts(ctx context.Context, namespaceKey, flagKey rtPercentageValue sql.NullBool rsSegmentKey sql.NullString rsSegmentValue sql.NullBool + rsSegmentOperator sql.NullInt32 rsMatchType sql.NullInt32 optionalConstraint optionalConstraint ) @@ -238,6 +334,7 @@ func (s *Store) GetEvaluationRollouts(ctx context.Context, namespaceKey, flagKey &rtPercentageValue, &rsSegmentKey, &rsSegmentValue, + &rsSegmentOperator, &rsMatchType, &optionalConstraint.Type, &optionalConstraint.Property, @@ -256,6 +353,7 @@ func (s *Store) GetEvaluationRollouts(ctx context.Context, namespaceKey, flagKey evaluationRollout.Threshold = storageThreshold } else if rsSegmentKey.Valid && rsSegmentValue.Valid && + rsSegmentOperator.Valid && rsMatchType.Valid { var c *storage.EvaluationConstraint @@ -268,23 +366,48 @@ func (s *Store) GetEvaluationRollouts(ctx context.Context, namespaceKey, flagKey } } - if existingSegment, ok := uniqueSegmentedRollouts[rolloutId]; ok { - if c != nil { - existingSegment.Segment.Constraints = append(existingSegment.Segment.Constraints, *c) + if existingRolloutSegment, ok := uniqueSegmentedRollouts[rolloutId]; ok { + // check if segment exists and either append constraints to an already existing segment, + // or add another segment to the map. + es, innerOk := existingRolloutSegment.Segment.Segments[rsSegmentKey.String] + if innerOk { + if c != nil { + es.Constraints = append(es.Constraints, *c) + } + } else { + + ses := &storage.EvaluationSegment{ + SegmentKey: rsSegmentKey.String, + MatchType: flipt.MatchType(rsMatchType.Int32), + } + + if c != nil { + ses.Constraints = []storage.EvaluationConstraint{*c} + } + + existingRolloutSegment.Segment.Segments[rsSegmentKey.String] = ses } + continue } storageSegment := &storage.RolloutSegment{ - Key: rsSegmentKey.String, - Value: rsSegmentValue.Bool, - MatchType: flipt.MatchType(rsMatchType.Int32), + Value: rsSegmentValue.Bool, + SegmentOperator: flipt.SegmentOperator(rsSegmentOperator.Int32), + Segments: make(map[string]*storage.EvaluationSegment), + } + + ses := &storage.EvaluationSegment{ + SegmentKey: rsSegmentKey.String, + MatchType: flipt.MatchType(rsMatchType.Int32), } if c != nil { - storageSegment.Constraints = append(storageSegment.Constraints, *c) + ses.Constraints = []storage.EvaluationConstraint{*c} } + storageSegment.Segments[rsSegmentKey.String] = ses + evaluationRollout.Segment = storageSegment uniqueSegmentedRollouts[rolloutId] = &evaluationRollout } diff --git a/internal/storage/sql/common/rollout.go b/internal/storage/sql/common/rollout.go index 9f3e120b2f..e4a2bc08c3 100644 --- a/internal/storage/sql/common/rollout.go +++ b/internal/storage/sql/common/rollout.go @@ -18,9 +18,10 @@ import ( ) const ( - tableRollouts = "rollouts" - tableRolloutPercentages = "rollout_thresholds" - tableRolloutSegments = "rollout_segments" + tableRollouts = "rollouts" + tableRolloutPercentages = "rollout_thresholds" + tableRolloutSegments = "rollout_segments" + tableRolloutSegmentReferences = "rollout_segment_references" ) func (s *Store) GetRollout(ctx context.Context, namespaceKey, id string) (*flipt.Rollout, error) { @@ -70,17 +71,61 @@ func getRollout(ctx context.Context, builder sq.StatementBuilderType, namespaceK Segment: &flipt.RolloutSegment{}, } - if err := builder.Select("segment_key, \"value\""). + var ( + value bool + rolloutSegmentId string + segmentOperator flipt.SegmentOperator + ) + if err := builder.Select("id, \"value\", segment_operator"). From(tableRolloutSegments). - Where(sq.And{sq.Eq{"rollout_id": rollout.Id}, sq.Eq{"namespace_key": rollout.NamespaceKey}}). + Where(sq.Eq{"rollout_id": rollout.Id}). Limit(1). QueryRowContext(ctx). - Scan( - &segmentRule.Segment.SegmentKey, - &segmentRule.Segment.Value); err != nil { + Scan(&rolloutSegmentId, &value, &segmentOperator); err != nil { + return nil, err + } + + segmentRule.Segment.Value = value + segmentRule.Segment.SegmentOperator = segmentOperator + + rows, err := builder.Select("segment_key"). + From(tableRolloutSegmentReferences). + Where(sq.Eq{"rollout_segment_id": rolloutSegmentId, "namespace_key": rollout.NamespaceKey}). + QueryContext(ctx) + if err != nil { + return nil, err + } + + defer func() { + if cerr := rows.Close(); cerr != nil && err == nil { + err = cerr + } + }() + + var segmentKeys = []string{} + + for rows.Next() { + var ( + segmentKey string + ) + + if err := rows.Scan(&segmentKey); err != nil { + return nil, err + } + + segmentKeys = append(segmentKeys, segmentKey) + } + + if err := rows.Err(); err != nil { return nil, err } + if len(segmentKeys) == 1 { + segmentRule.Segment.SegmentKey = segmentKeys[0] + } else { + segmentRule.Segment.SegmentKeys = segmentKeys + } + rollout.Rule = segmentRule case flipt.RolloutType_THRESHOLD_ROLLOUT_TYPE: var thresholdRule = &flipt.Rollout_Threshold{ @@ -202,8 +247,9 @@ func (s *Store) ListRollouts(ctx context.Context, namespaceKey, flagKey string, allRuleIds = append(allRuleIds, rollout.Id) } - rows, err := s.builder.Select("rollout_id, segment_key, \"value\""). - From(tableRolloutSegments). + rows, err := s.builder.Select("rs.rollout_id, rs.\"value\", rs.segment_operator, rsr.segment_key"). + From("rollout_segments AS rs"). + Join("rollout_segment_references AS rsr ON (rs.id = rsr.rollout_segment_id)"). Where(sq.Eq{"rollout_id": allRuleIds}). QueryContext(ctx) @@ -217,18 +263,52 @@ func (s *Store) ListRollouts(ctx context.Context, namespaceKey, flagKey string, } }() + type intermediateValues struct { + segmentKeys []string + segmentOperator flipt.SegmentOperator + value bool + } + + intermediate := make(map[string]*intermediateValues) + for rows.Next() { var ( - rolloutId string - rule = &flipt.RolloutSegment{} + rolloutId string + segmentKey string + value bool + segmentOperator flipt.SegmentOperator ) - if err := rows.Scan(&rolloutId, &rule.SegmentKey, &rule.Value); err != nil { + if err := rows.Scan(&rolloutId, &value, &segmentOperator, &segmentKey); err != nil { return results, err } - rollout := rolloutsById[rolloutId] - rollout.Rule = &flipt.Rollout_Segment{Segment: rule} + rs, ok := intermediate[rolloutId] + if ok { + rs.segmentKeys = append(rs.segmentKeys, segmentKey) + } else { + intermediate[rolloutId] = &intermediateValues{ + segmentKeys: []string{segmentKey}, + segmentOperator: segmentOperator, + value: value, + } + } + } + + for k, v := range intermediate { + rollout := rolloutsById[k] + rs := &flipt.RolloutSegment{} + + if len(v.segmentKeys) == 1 { + rs.SegmentKey = v.segmentKeys[0] + } else { + rs.SegmentKeys = v.segmentKeys + } + + rs.Value = v.value + rs.SegmentOperator = v.segmentOperator + + rollout.Rule = &flipt.Rollout_Segment{Segment: rs} } if err := rows.Err(); err != nil { @@ -383,19 +463,48 @@ func (s *Store) CreateRollout(ctx context.Context, r *flipt.CreateRolloutRequest switch r.GetRule().(type) { case *flipt.CreateRolloutRequest_Segment: rollout.Type = flipt.RolloutType_SEGMENT_ROLLOUT_TYPE + rolloutSegmentId := uuid.Must(uuid.NewV4()).String() var segmentRule = r.GetSegment() + segmentKeys := sanitizeSegmentKeys(segmentRule.GetSegmentKey(), segmentRule.GetSegmentKeys()) + + var segmentOperator = segmentRule.SegmentOperator + if len(segmentKeys) == 1 { + segmentOperator = flipt.SegmentOperator_OR_SEGMENT_OPERATOR + } + if _, err := s.builder.Insert(tableRolloutSegments). RunWith(tx). - Columns("id", "rollout_id", "namespace_key", "segment_key", "\"value\""). - Values(uuid.Must(uuid.NewV4()).String(), rollout.Id, rollout.NamespaceKey, segmentRule.SegmentKey, segmentRule.Value). + Columns("id", "rollout_id", "\"value\"", "segment_operator"). + Values(rolloutSegmentId, rollout.Id, segmentRule.Value, segmentOperator). ExecContext(ctx); err != nil { return nil, err } + for _, segmentKey := range segmentKeys { + if _, err := s.builder.Insert(tableRolloutSegmentReferences). + RunWith(tx). + Columns("rollout_segment_id", "namespace_key", "segment_key"). + Values(rolloutSegmentId, rollout.NamespaceKey, segmentKey). + ExecContext(ctx); err != nil { + return nil, err + } + } + + innerSegment := &flipt.RolloutSegment{ + Value: segmentRule.Value, + SegmentOperator: segmentOperator, + } + + if len(segmentKeys) == 1 { + innerSegment.SegmentKey = segmentKeys[0] + } else { + innerSegment.SegmentKeys = segmentKeys + } + rollout.Rule = &flipt.Rollout_Segment{ - Segment: segmentRule, + Segment: innerSegment, } case *flipt.CreateRolloutRequest_Threshold: rollout.Type = flipt.RolloutType_THRESHOLD_ROLLOUT_TYPE @@ -477,13 +586,57 @@ func (s *Store) UpdateRollout(ctx context.Context, r *flipt.UpdateRolloutRequest var segmentRule = r.GetSegment() + segmentKeys := sanitizeSegmentKeys(segmentRule.GetSegmentKey(), segmentRule.GetSegmentKeys()) + + var segmentOperator = segmentRule.SegmentOperator + if len(segmentKeys) == 1 { + segmentOperator = flipt.SegmentOperator_OR_SEGMENT_OPERATOR + } + if _, err := s.builder.Update(tableRolloutSegments). RunWith(tx). - Set("segment_key", segmentRule.SegmentKey). + Set("segment_operator", segmentOperator). Set("value", segmentRule.Value). Where(sq.Eq{"rollout_id": r.Id}).ExecContext(ctx); err != nil { return nil, err } + + // Delete and reinsert rollout_segment_references. + row := s.builder.Select("id"). + RunWith(tx). + From(tableRolloutSegments). + Where(sq.Eq{"rollout_id": r.Id}). + Limit(1). + QueryRowContext(ctx) + + var rolloutSegmentId string + + if err := row.Scan(&rolloutSegmentId); err != nil { + return nil, err + } + + if _, err := s.builder.Delete(tableRolloutSegmentReferences). + RunWith(tx). + Where(sq.And{sq.Eq{"rollout_segment_id": rolloutSegmentId}, sq.Eq{"namespace_key": r.NamespaceKey}}). + ExecContext(ctx); err != nil { + return nil, err + } + + for _, segmentKey := range segmentKeys { + if _, err := s.builder. + Insert(tableRolloutSegmentReferences). + RunWith(tx). + Columns("rollout_segment_id", "namespace_key", "segment_key"). + Values( + rolloutSegmentId, + r.NamespaceKey, + segmentKey, + ). + ExecContext(ctx); err != nil { + return nil, err + } + } + case *flipt.UpdateRolloutRequest_Threshold: // enforce that rollout type is consistent with the DB if err := ensureRolloutType(rollout, flipt.RolloutType_THRESHOLD_ROLLOUT_TYPE); err != nil { @@ -503,12 +656,16 @@ func (s *Store) UpdateRollout(ctx context.Context, r *flipt.UpdateRolloutRequest return nil, errs.InvalidFieldError("rule", "invalid rollout rule type") } - rollout, err = getRollout(ctx, s.builder.RunWith(tx), r.NamespaceKey, r.Id) + if err = tx.Commit(); err != nil { + return nil, err + } + + rollout, err = getRollout(ctx, s.builder, r.NamespaceKey, r.Id) if err != nil { return nil, err } - return rollout, tx.Commit() + return rollout, nil } func ensureRolloutType(rollout *flipt.Rollout, typ flipt.RolloutType) error { @@ -523,7 +680,7 @@ func ensureRolloutType(rollout *flipt.Rollout, typ flipt.RolloutType) error { ) } -func (s *Store) DeleteRollout(ctx context.Context, r *flipt.DeleteRolloutRequest) error { +func (s *Store) DeleteRollout(ctx context.Context, r *flipt.DeleteRolloutRequest) (err error) { if r.NamespaceKey == "" { r.NamespaceKey = storage.DefaultNamespace } diff --git a/internal/storage/sql/common/rule.go b/internal/storage/sql/common/rule.go index 17ed1cb1b1..92e8bfa1a7 100644 --- a/internal/storage/sql/common/rule.go +++ b/internal/storage/sql/common/rule.go @@ -30,11 +30,11 @@ func (s *Store) GetRule(ctx context.Context, namespaceKey, id string) (*flipt.Ru rule = &flipt.Rule{} - err = s.builder.Select("id, namespace_key, flag_key, segment_key, \"rank\", created_at, updated_at"). + err = s.builder.Select("id, namespace_key, flag_key, \"rank\", segment_operator, created_at, updated_at"). From("rules"). Where(sq.And{sq.Eq{"id": id}, sq.Eq{"namespace_key": namespaceKey}}). QueryRowContext(ctx). - Scan(&rule.Id, &rule.NamespaceKey, &rule.FlagKey, &rule.SegmentKey, &rule.Rank, &createdAt, &updatedAt) + Scan(&rule.Id, &rule.NamespaceKey, &rule.FlagKey, &rule.Rank, &rule.SegmentOperator, &createdAt, &updatedAt) ) if err != nil { @@ -45,6 +45,42 @@ func (s *Store) GetRule(ctx context.Context, namespaceKey, id string) (*flipt.Ru return nil, err } + segmentRows, err := s.builder.Select("segment_key"). + From("rule_segments"). + Where(sq.Eq{"rule_id": rule.Id}). + QueryContext(ctx) + + defer func() { + if cerr := segmentRows.Close(); cerr != nil && err == nil { + err = cerr + } + }() + + segmentKeys := make([]string, 0) + for segmentRows.Next() { + var segmentKey string + + if err := segmentRows.Scan(&segmentKey); err != nil { + return nil, err + } + + segmentKeys = append(segmentKeys, segmentKey) + } + + if err := segmentRows.Err(); err != nil { + return nil, err + } + + if err := segmentRows.Close(); err != nil { + return nil, err + } + + if len(segmentKeys) == 1 { + rule.SegmentKey = segmentKeys[0] + } else { + rule.SegmentKeys = segmentKeys + } + rule.CreatedAt = createdAt.Timestamp rule.UpdatedAt = updatedAt.Timestamp @@ -114,7 +150,7 @@ func (s *Store) ListRules(ctx context.Context, namespaceKey, flagKey string, opt rules []*flipt.Rule results = storage.ResultSet[*flipt.Rule]{} - query = s.builder.Select("id, namespace_key, flag_key, segment_key, \"rank\", created_at, updated_at"). + query = s.builder.Select("id, namespace_key, flag_key, \"rank\", segment_operator, created_at, updated_at"). From("rules"). Where(sq.Eq{"flag_key": flagKey, "namespace_key": namespaceKey}). OrderBy(fmt.Sprintf("\"rank\" %s", params.Order)) @@ -162,10 +198,11 @@ func (s *Store) ListRules(ctx context.Context, namespaceKey, flagKey string, opt &rule.Id, &rule.NamespaceKey, &rule.FlagKey, - &rule.SegmentKey, &rule.Rank, + &rule.SegmentOperator, &rCreatedAt, - &rUpdatedAt); err != nil { + &rUpdatedAt, + ); err != nil { return results, err } @@ -184,6 +221,46 @@ func (s *Store) ListRules(ctx context.Context, namespaceKey, flagKey string, opt return results, err } + // For each rule, find the segment keys and add them to the rule proto definition. + for _, r := range rules { + // Since we are querying within a loop, we do not want to defer within the loop + // so we will disable the sqlclosecheck linter. + segmentRows, err := s.builder.Select("segment_key"). + From("rule_segments"). + Where(sq.Eq{"rule_id": r.Id}). + QueryContext(ctx) + if err != nil { + //nolint:sqlclosecheck + _ = segmentRows.Close() + return results, err + } + + segmentKeys := make([]string, 0) + for segmentRows.Next() { + var segmentKey string + if err := segmentRows.Scan(&segmentKey); err != nil { + return results, err + } + + segmentKeys = append(segmentKeys, segmentKey) + } + + if err := segmentRows.Err(); err != nil { + return results, err + } + + //nolint:sqlclosecheck + if err := segmentRows.Close(); err != nil { + return results, err + } + + if len(segmentKeys) == 1 { + r.SegmentKey = segmentKeys[0] + } else { + r.SegmentKeys = segmentKeys + } + } + if err := s.setDistributions(ctx, rulesById); err != nil { return results, err } @@ -243,7 +320,8 @@ func (s *Store) setDistributions(ctx context.Context, rulesById map[string]*flip &distribution.VariantId, &distribution.Rollout, &dCreatedAt, - &dUpdatedAt); err != nil { + &dUpdatedAt, + ); err != nil { return err } @@ -286,7 +364,9 @@ func (s *Store) CountRules(ctx context.Context, namespaceKey, flagKey string) (u } // CreateRule creates a rule -func (s *Store) CreateRule(ctx context.Context, r *flipt.CreateRuleRequest) (*flipt.Rule, error) { +func (s *Store) CreateRule(ctx context.Context, r *flipt.CreateRuleRequest) (_ *flipt.Rule, err error) { + segmentKeys := sanitizeSegmentKeys(r.GetSegmentKey(), r.GetSegmentKeys()) + if r.NamespaceKey == "" { r.NamespaceKey = storage.DefaultNamespace } @@ -294,57 +374,133 @@ func (s *Store) CreateRule(ctx context.Context, r *flipt.CreateRuleRequest) (*fl var ( now = timestamppb.Now() rule = &flipt.Rule{ - Id: uuid.Must(uuid.NewV4()).String(), - NamespaceKey: r.NamespaceKey, - FlagKey: r.FlagKey, - SegmentKey: r.SegmentKey, - Rank: r.Rank, - CreatedAt: now, - UpdatedAt: now, + Id: uuid.Must(uuid.NewV4()).String(), + NamespaceKey: r.NamespaceKey, + FlagKey: r.FlagKey, + Rank: r.Rank, + SegmentOperator: r.SegmentOperator, + CreatedAt: now, + UpdatedAt: now, } ) + // Force segment operator to be OR when `segmentKeys` length is 1. + if len(segmentKeys) == 1 { + rule.SegmentOperator = flipt.SegmentOperator_OR_SEGMENT_OPERATOR + } + + tx, err := s.db.Begin() + if err != nil { + return nil, err + } + + defer func() { + if err != nil { + _ = tx.Rollback() + } + }() + if _, err := s.builder. Insert("rules"). - Columns("id", "namespace_key", "flag_key", "segment_key", "\"rank\"", "created_at", "updated_at"). + RunWith(tx). + Columns("id", "namespace_key", "flag_key", "\"rank\"", "segment_operator", "created_at", "updated_at"). Values( rule.Id, rule.NamespaceKey, rule.FlagKey, - rule.SegmentKey, rule.Rank, + rule.SegmentOperator, &fliptsql.Timestamp{Timestamp: rule.CreatedAt}, - &fliptsql.Timestamp{Timestamp: rule.UpdatedAt}). + &fliptsql.Timestamp{Timestamp: rule.UpdatedAt}, + ). ExecContext(ctx); err != nil { return nil, err } - return rule, nil + for _, segmentKey := range segmentKeys { + if _, err := s.builder. + Insert("rule_segments"). + RunWith(tx). + Columns("rule_id", "namespace_key", "segment_key"). + Values( + rule.Id, + rule.NamespaceKey, + segmentKey, + ). + ExecContext(ctx); err != nil { + return nil, err + } + } + + if len(segmentKeys) == 1 { + rule.SegmentKey = segmentKeys[0] + } else { + rule.SegmentKeys = segmentKeys + } + + return rule, tx.Commit() } // UpdateRule updates an existing rule -func (s *Store) UpdateRule(ctx context.Context, r *flipt.UpdateRuleRequest) (*flipt.Rule, error) { +func (s *Store) UpdateRule(ctx context.Context, r *flipt.UpdateRuleRequest) (_ *flipt.Rule, err error) { + segmentKeys := sanitizeSegmentKeys(r.GetSegmentKey(), r.GetSegmentKeys()) + if r.NamespaceKey == "" { r.NamespaceKey = storage.DefaultNamespace } - query := s.builder.Update("rules"). - Set("segment_key", r.SegmentKey). - Set("updated_at", &fliptsql.Timestamp{Timestamp: timestamppb.Now()}). - Where(sq.And{sq.Eq{"id": r.Id}, sq.Eq{"namespace_key": r.NamespaceKey}, sq.Eq{"flag_key": r.FlagKey}}) - - res, err := query.ExecContext(ctx) + tx, err := s.db.Begin() if err != nil { return nil, err } - count, err := res.RowsAffected() + defer func() { + if err != nil { + _ = tx.Rollback() + } + }() + + var segmentOperator = r.SegmentOperator + if len(segmentKeys) == 1 { + segmentOperator = flipt.SegmentOperator_OR_SEGMENT_OPERATOR + } + + // Set segment operator. + _, err = s.builder.Update("rules"). + RunWith(tx). + Set("segment_operator", segmentOperator). + Set("updated_at", &fliptsql.Timestamp{Timestamp: timestamppb.Now()}). + Where(sq.Eq{"id": r.Id, "namespace_key": r.NamespaceKey, "flag_key": r.FlagKey}). + ExecContext(ctx) if err != nil { return nil, err } - if count != 1 { - return nil, errs.ErrNotFoundf(`rule "%s/%s"`, r.NamespaceKey, r.Id) + // Delete and reinsert segmentKeys. + if _, err = s.builder.Delete("rule_segments"). + RunWith(tx). + Where(sq.And{sq.Eq{"rule_id": r.Id}, sq.Eq{"namespace_key": r.NamespaceKey}}). + ExecContext(ctx); err != nil { + return nil, err + } + + for _, segmentKey := range segmentKeys { + if _, err := s.builder. + Insert("rule_segments"). + RunWith(tx). + Columns("rule_id", "namespace_key", "segment_key"). + Values( + r.Id, + r.NamespaceKey, + segmentKey, + ). + ExecContext(ctx); err != nil { + return nil, err + } + } + + if err = tx.Commit(); err != nil { + return nil, err } return s.GetRule(ctx, r.NamespaceKey, r.Id) diff --git a/internal/storage/sql/common/util.go b/internal/storage/sql/common/util.go index 316ba98524..4fcd954fd6 100644 --- a/internal/storage/sql/common/util.go +++ b/internal/storage/sql/common/util.go @@ -27,3 +27,30 @@ func decodePageToken(logger *zap.Logger, pageToken string) (PageToken, error) { return token, nil } + +// removeDuplicates is an inner utility function that will deduplicate a slice of strings. +func removeDuplicates(src []string) []string { + allKeys := make(map[string]bool) + + dest := []string{} + + for _, item := range src { + if _, value := allKeys[item]; !value { + allKeys[item] = true + dest = append(dest, item) + } + } + + return dest +} + +// sanitizeSegmentKeys is a utility function that will transform segment keys into the right input. +func sanitizeSegmentKeys(segmentKey string, segmentKeys []string) []string { + if len(segmentKeys) > 0 { + return removeDuplicates(segmentKeys) + } else if segmentKey != "" { + return []string{segmentKey} + } + + return nil +} diff --git a/internal/storage/sql/evaluation_test.go b/internal/storage/sql/evaluation_test.go index 94ce6864d8..3074aa717a 100644 --- a/internal/storage/sql/evaluation_test.go +++ b/internal/storage/sql/evaluation_test.go @@ -65,18 +65,18 @@ func (s *DBTestSuite) TestGetEvaluationRules() { // rule rank 1 rule1, err := s.store.CreateRule(context.TODO(), &flipt.CreateRuleRequest{ - FlagKey: flag.Key, - SegmentKey: segment.Key, - Rank: 1, + FlagKey: flag.Key, + SegmentKeys: []string{segment.Key}, + Rank: 1, }) require.NoError(t, err) // rule rank 2 rule2, err := s.store.CreateRule(context.TODO(), &flipt.CreateRuleRequest{ - FlagKey: flag.Key, - SegmentKey: segment.Key, - Rank: 2, + FlagKey: flag.Key, + SegmentKeys: []string{segment.Key}, + Rank: 2, }) require.NoError(t, err) @@ -90,19 +90,22 @@ func (s *DBTestSuite) TestGetEvaluationRules() { assert.Equal(t, rule1.Id, evaluationRules[0].ID) assert.Equal(t, storage.DefaultNamespace, evaluationRules[0].NamespaceKey) assert.Equal(t, rule1.FlagKey, evaluationRules[0].FlagKey) - assert.Equal(t, rule1.SegmentKey, evaluationRules[0].SegmentKey) - assert.Equal(t, segment.MatchType, evaluationRules[0].SegmentMatchType) + + assert.Equal(t, rule1.SegmentKey, evaluationRules[0].Segments[segment.Key].SegmentKey) + assert.Equal(t, segment.MatchType, evaluationRules[0].Segments[segment.Key].MatchType) assert.Equal(t, rule1.Rank, evaluationRules[0].Rank) - assert.Equal(t, 2, len(evaluationRules[0].Constraints)) + assert.Len(t, evaluationRules[0].Segments[segment.Key].Constraints, 2) assert.Equal(t, rule2.Id, evaluationRules[1].ID) assert.Equal(t, storage.DefaultNamespace, evaluationRules[1].NamespaceKey) assert.Equal(t, rule2.FlagKey, evaluationRules[1].FlagKey) - assert.Equal(t, rule2.SegmentKey, evaluationRules[1].SegmentKey) - assert.Equal(t, segment.MatchType, evaluationRules[1].SegmentMatchType) + + assert.Equal(t, rule2.SegmentKey, evaluationRules[1].Segments[segment.Key].SegmentKey) + assert.Equal(t, segment.MatchType, evaluationRules[1].Segments[segment.Key].MatchType) assert.Equal(t, rule2.Rank, evaluationRules[1].Rank) - assert.Equal(t, 2, len(evaluationRules[1].Constraints)) + assert.Len(t, evaluationRules[1].Segments[segment.Key].Constraints, 2) } + func (s *DBTestSuite) TestGetEvaluationRules_NoNamespace() { t := s.T() @@ -148,18 +151,18 @@ func (s *DBTestSuite) TestGetEvaluationRules_NoNamespace() { // rule rank 1 rule1, err := s.store.CreateRule(context.TODO(), &flipt.CreateRuleRequest{ - FlagKey: flag.Key, - SegmentKey: segment.Key, - Rank: 1, + FlagKey: flag.Key, + SegmentKeys: []string{segment.Key}, + Rank: 1, }) require.NoError(t, err) // rule rank 2 rule2, err := s.store.CreateRule(context.TODO(), &flipt.CreateRuleRequest{ - FlagKey: flag.Key, - SegmentKey: segment.Key, - Rank: 2, + FlagKey: flag.Key, + SegmentKeys: []string{segment.Key}, + Rank: 2, }) require.NoError(t, err) @@ -173,18 +176,20 @@ func (s *DBTestSuite) TestGetEvaluationRules_NoNamespace() { assert.Equal(t, rule1.Id, evaluationRules[0].ID) assert.Equal(t, storage.DefaultNamespace, evaluationRules[0].NamespaceKey) assert.Equal(t, rule1.FlagKey, evaluationRules[0].FlagKey) - assert.Equal(t, rule1.SegmentKey, evaluationRules[0].SegmentKey) - assert.Equal(t, segment.MatchType, evaluationRules[0].SegmentMatchType) + + assert.Equal(t, rule1.SegmentKey, evaluationRules[0].Segments[segment.Key].SegmentKey) + assert.Equal(t, segment.MatchType, evaluationRules[0].Segments[segment.Key].MatchType) assert.Equal(t, rule1.Rank, evaluationRules[0].Rank) - assert.Equal(t, 2, len(evaluationRules[0].Constraints)) + assert.Len(t, evaluationRules[0].Segments[segment.Key].Constraints, 2) assert.Equal(t, rule2.Id, evaluationRules[1].ID) assert.Equal(t, storage.DefaultNamespace, evaluationRules[1].NamespaceKey) assert.Equal(t, rule2.FlagKey, evaluationRules[1].FlagKey) - assert.Equal(t, rule2.SegmentKey, evaluationRules[1].SegmentKey) - assert.Equal(t, segment.MatchType, evaluationRules[1].SegmentMatchType) + + assert.Equal(t, rule2.SegmentKey, evaluationRules[1].Segments[segment.Key].SegmentKey) + assert.Equal(t, segment.MatchType, evaluationRules[1].Segments[segment.Key].MatchType) assert.Equal(t, rule2.Rank, evaluationRules[1].Rank) - assert.Equal(t, 2, len(evaluationRules[1].Constraints)) + assert.Len(t, evaluationRules[1].Segments[segment.Key].Constraints, 2) } func (s *DBTestSuite) TestGetEvaluationRulesNamespace() { @@ -200,7 +205,7 @@ func (s *DBTestSuite) TestGetEvaluationRulesNamespace() { require.NoError(t, err) - segment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + firstSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ NamespaceKey: s.namespace, Key: t.Name(), Name: "foo", @@ -210,10 +215,20 @@ func (s *DBTestSuite) TestGetEvaluationRulesNamespace() { require.NoError(t, err) + secondSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + NamespaceKey: s.namespace, + Key: "another_segment", + Name: "foo", + Description: "bar", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + }) + + require.NoError(t, err) + // constraint 1 _, err = s.store.CreateConstraint(context.TODO(), &flipt.CreateConstraintRequest{ NamespaceKey: s.namespace, - SegmentKey: segment.Key, + SegmentKey: firstSegment.Key, Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, Property: "foo", Operator: "EQ", @@ -225,7 +240,7 @@ func (s *DBTestSuite) TestGetEvaluationRulesNamespace() { // constraint 2 _, err = s.store.CreateConstraint(context.TODO(), &flipt.CreateConstraintRequest{ NamespaceKey: s.namespace, - SegmentKey: segment.Key, + SegmentKey: firstSegment.Key, Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, Property: "foz", Operator: "EQ", @@ -236,20 +251,11 @@ func (s *DBTestSuite) TestGetEvaluationRulesNamespace() { // rule rank 1 rule1, err := s.store.CreateRule(context.TODO(), &flipt.CreateRuleRequest{ - NamespaceKey: s.namespace, - FlagKey: flag.Key, - SegmentKey: segment.Key, - Rank: 1, - }) - - require.NoError(t, err) - - // rule rank 2 - rule2, err := s.store.CreateRule(context.TODO(), &flipt.CreateRuleRequest{ - NamespaceKey: s.namespace, - FlagKey: flag.Key, - SegmentKey: segment.Key, - Rank: 2, + NamespaceKey: s.namespace, + FlagKey: flag.Key, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + SegmentKeys: []string{firstSegment.Key, secondSegment.Key}, + Rank: 1, }) require.NoError(t, err) @@ -258,23 +264,21 @@ func (s *DBTestSuite) TestGetEvaluationRulesNamespace() { require.NoError(t, err) assert.NotEmpty(t, evaluationRules) - assert.Equal(t, 2, len(evaluationRules)) + assert.Equal(t, 1, len(evaluationRules)) assert.Equal(t, rule1.Id, evaluationRules[0].ID) assert.Equal(t, s.namespace, evaluationRules[0].NamespaceKey) assert.Equal(t, rule1.FlagKey, evaluationRules[0].FlagKey) - assert.Equal(t, rule1.SegmentKey, evaluationRules[0].SegmentKey) - assert.Equal(t, segment.MatchType, evaluationRules[0].SegmentMatchType) + + assert.Equal(t, firstSegment.Key, evaluationRules[0].Segments[firstSegment.Key].SegmentKey) + assert.Equal(t, firstSegment.MatchType, evaluationRules[0].Segments[firstSegment.Key].MatchType) assert.Equal(t, rule1.Rank, evaluationRules[0].Rank) - assert.Equal(t, 2, len(evaluationRules[0].Constraints)) + assert.Len(t, evaluationRules[0].Segments[firstSegment.Key].Constraints, 2) - assert.Equal(t, rule2.Id, evaluationRules[1].ID) - assert.Equal(t, s.namespace, evaluationRules[1].NamespaceKey) - assert.Equal(t, rule2.FlagKey, evaluationRules[1].FlagKey) - assert.Equal(t, rule2.SegmentKey, evaluationRules[1].SegmentKey) - assert.Equal(t, segment.MatchType, evaluationRules[1].SegmentMatchType) - assert.Equal(t, rule2.Rank, evaluationRules[1].Rank) - assert.Equal(t, 2, len(evaluationRules[1].Constraints)) + assert.Equal(t, secondSegment.Key, evaluationRules[0].Segments[secondSegment.Key].SegmentKey) + assert.Equal(t, secondSegment.MatchType, evaluationRules[0].Segments[secondSegment.Key].MatchType) + assert.Equal(t, rule1.Rank, evaluationRules[0].Rank) + assert.Len(t, evaluationRules[0].Segments[secondSegment.Key].Constraints, 0) } func (s *DBTestSuite) TestGetEvaluationDistributions() { @@ -657,8 +661,8 @@ func (s *DBTestSuite) TestGetEvaluationRollouts() { Rank: 2, Rule: &flipt.CreateRolloutRequest_Segment{ Segment: &flipt.RolloutSegment{ - SegmentKey: segment.Key, - Value: true, + SegmentKeys: []string{segment.Key}, + Value: true, }, }, }) @@ -679,8 +683,10 @@ func (s *DBTestSuite) TestGetEvaluationRollouts() { assert.Equal(t, "default", evaluationRollouts[1].NamespaceKey) assert.Equal(t, int32(2), evaluationRollouts[1].Rank) assert.NotNil(t, evaluationRollouts[1].Segment) - assert.Equal(t, segment.Key, evaluationRollouts[1].Segment.Key) - assert.Equal(t, segment.MatchType, evaluationRollouts[1].Segment.MatchType) + + assert.Contains(t, evaluationRollouts[1].Segment.Segments, segment.Key) + assert.Equal(t, segment.MatchType, evaluationRollouts[1].Segment.Segments[segment.Key].MatchType) + assert.True(t, evaluationRollouts[1].Segment.Value, "segment value is true") } @@ -697,7 +703,7 @@ func (s *DBTestSuite) TestGetEvaluationRollouts_NoNamespace() { require.NoError(t, err) - segment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + firstSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ Key: t.Name(), Name: "foo", Description: "bar", @@ -706,8 +712,17 @@ func (s *DBTestSuite) TestGetEvaluationRollouts_NoNamespace() { require.NoError(t, err) + secondSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: "another_segment", + Name: "foo", + Description: "bar", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + }) + + require.NoError(t, err) + _, err = s.store.CreateConstraint(context.TODO(), &flipt.CreateConstraintRequest{ - SegmentKey: segment.Key, + SegmentKey: firstSegment.Key, Type: flipt.ComparisonType_STRING_COMPARISON_TYPE, Property: "foo", Operator: "EQ", @@ -734,8 +749,9 @@ func (s *DBTestSuite) TestGetEvaluationRollouts_NoNamespace() { Rank: 2, Rule: &flipt.CreateRolloutRequest_Segment{ Segment: &flipt.RolloutSegment{ - SegmentKey: segment.Key, - Value: true, + SegmentKeys: []string{firstSegment.Key, secondSegment.Key}, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + Value: true, }, }, }) @@ -756,9 +772,13 @@ func (s *DBTestSuite) TestGetEvaluationRollouts_NoNamespace() { assert.Equal(t, "default", evaluationRollouts[1].NamespaceKey) assert.Equal(t, int32(2), evaluationRollouts[1].Rank) assert.NotNil(t, evaluationRollouts[1].Segment) - assert.Equal(t, segment.Key, evaluationRollouts[1].Segment.Key) - assert.Equal(t, segment.MatchType, evaluationRollouts[1].Segment.MatchType) - assert.True(t, evaluationRollouts[1].Segment.Value, "segment value is true") + + assert.Equal(t, firstSegment.Key, evaluationRollouts[1].Segment.Segments[firstSegment.Key].SegmentKey) + assert.Equal(t, flipt.SegmentOperator_AND_SEGMENT_OPERATOR, evaluationRollouts[1].Segment.SegmentOperator) + assert.Len(t, evaluationRollouts[1].Segment.Segments[firstSegment.Key].Constraints, 1) + + assert.Equal(t, secondSegment.Key, evaluationRollouts[1].Segment.Segments[secondSegment.Key].SegmentKey) + assert.Len(t, evaluationRollouts[1].Segment.Segments[secondSegment.Key].Constraints, 0) } func (s *DBTestSuite) TestGetEvaluationRollouts_NonDefaultNamespace() { @@ -838,8 +858,10 @@ func (s *DBTestSuite) TestGetEvaluationRollouts_NonDefaultNamespace() { assert.Equal(t, s.namespace, evaluationRollouts[1].NamespaceKey) assert.Equal(t, int32(2), evaluationRollouts[1].Rank) assert.NotNil(t, evaluationRollouts[1].Segment) - assert.Equal(t, segment.Key, evaluationRollouts[1].Segment.Key) - assert.Equal(t, segment.MatchType, evaluationRollouts[1].Segment.MatchType) + + assert.Contains(t, evaluationRollouts[1].Segment.Segments, segment.Key) + assert.Equal(t, segment.MatchType, evaluationRollouts[1].Segment.Segments[segment.Key].MatchType) + assert.True(t, evaluationRollouts[1].Segment.Value, "segment value is true") } diff --git a/internal/storage/sql/migrator.go b/internal/storage/sql/migrator.go index 262f2176bf..f1ad9d9280 100644 --- a/internal/storage/sql/migrator.go +++ b/internal/storage/sql/migrator.go @@ -18,10 +18,10 @@ import ( ) var expectedVersions = map[Driver]uint{ - SQLite: 10, - Postgres: 10, - MySQL: 8, - CockroachDB: 7, + SQLite: 11, + Postgres: 12, + MySQL: 10, + CockroachDB: 9, } // Migrator is responsible for migrating the database schema diff --git a/internal/storage/sql/mysql/mysql.go b/internal/storage/sql/mysql/mysql.go index 4c6cbaa804..5d65758df1 100644 --- a/internal/storage/sql/mysql/mysql.go +++ b/internal/storage/sql/mysql/mysql.go @@ -182,6 +182,22 @@ func (s *Store) CreateRule(ctx context.Context, r *flipt.CreateRuleRequest) (*fl return rule, nil } +func (s *Store) UpdateRule(ctx context.Context, r *flipt.UpdateRuleRequest) (*flipt.Rule, error) { + rule, err := s.Store.UpdateRule(ctx, r) + + if err != nil { + var merr *mysql.MySQLError + + if errors.As(err, &merr) && merr.Number == constraintForeignKeyErrCode { + return nil, errs.ErrNotFoundf(`rule "%s/%s"`, r.NamespaceKey, r.Id) + } + + return nil, err + } + + return rule, nil +} + func (s *Store) CreateDistribution(ctx context.Context, r *flipt.CreateDistributionRequest) (*flipt.Distribution, error) { dist, err := s.Store.CreateDistribution(ctx, r) diff --git a/internal/storage/sql/postgres/postgres.go b/internal/storage/sql/postgres/postgres.go index ebb8e51037..9caa207f88 100644 --- a/internal/storage/sql/postgres/postgres.go +++ b/internal/storage/sql/postgres/postgres.go @@ -182,6 +182,22 @@ func (s *Store) CreateRule(ctx context.Context, r *flipt.CreateRuleRequest) (*fl return rule, nil } +func (s *Store) UpdateRule(ctx context.Context, r *flipt.UpdateRuleRequest) (*flipt.Rule, error) { + rule, err := s.Store.UpdateRule(ctx, r) + + if err != nil { + var perr *pq.Error + + if errors.As(err, &perr) && perr.Code.Name() == constraintForeignKeyErr { + return nil, errs.ErrNotFoundf(`rule "%s/%s"`, r.NamespaceKey, r.Id) + } + + return nil, err + } + + return rule, nil +} + func (s *Store) CreateDistribution(ctx context.Context, r *flipt.CreateDistributionRequest) (*flipt.Distribution, error) { dist, err := s.Store.CreateDistribution(ctx, r) diff --git a/internal/storage/sql/rollout_test.go b/internal/storage/sql/rollout_test.go index 0cebf68572..490fee2b71 100644 --- a/internal/storage/sql/rollout_test.go +++ b/internal/storage/sql/rollout_test.go @@ -141,15 +141,6 @@ func (s *DBTestSuite) TestListRollouts() { require.NoError(t, err) assert.NotNil(t, variant) - segment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ - Key: t.Name(), - Name: "foo", - Description: "bar", - }) - - require.NoError(t, err) - assert.NotNil(t, segment) - reqs := []*flipt.CreateRolloutRequest{ { FlagKey: flag.Key, @@ -191,6 +182,85 @@ func (s *DBTestSuite) TestListRollouts() { } } +func (s *DBTestSuite) TestListRollouts_MultipleSegments() { + t := s.T() + + flag, err := s.store.CreateFlag(context.TODO(), &flipt.CreateFlagRequest{ + Key: t.Name(), + Name: "foo", + Description: "bar", + Enabled: true, + Type: flipt.FlagType_BOOLEAN_FLAG_TYPE, + }) + + require.NoError(t, err) + assert.NotNil(t, flag) + + firstSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: t.Name(), + Name: "foo", + Description: "bar", + }) + + require.NoError(t, err) + assert.NotNil(t, firstSegment) + + secondSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: "another_segment_3", + Name: "foo", + Description: "bar", + }) + + require.NoError(t, err) + assert.NotNil(t, secondSegment) + + reqs := []*flipt.CreateRolloutRequest{ + { + FlagKey: flag.Key, + Rank: 1, + Rule: &flipt.CreateRolloutRequest_Segment{ + Segment: &flipt.RolloutSegment{ + Value: true, + SegmentKeys: []string{firstSegment.Key, secondSegment.Key}, + }, + }, + }, + { + FlagKey: flag.Key, + Rank: 2, + Rule: &flipt.CreateRolloutRequest_Segment{ + Segment: &flipt.RolloutSegment{ + Value: true, + SegmentKeys: []string{firstSegment.Key, secondSegment.Key}, + }, + }, + }, + } + + for _, req := range reqs { + _, err := s.store.CreateRollout(context.TODO(), req) + require.NoError(t, err) + } + + res, err := s.store.ListRollouts(context.TODO(), storage.DefaultNamespace, flag.Key) + require.NoError(t, err) + + got := res.Results + assert.NotZero(t, len(got)) + + for _, rollout := range got { + assert.Equal(t, storage.DefaultNamespace, rollout.NamespaceKey) + + rs, ok := rollout.Rule.(*flipt.Rollout_Segment) + assert.True(t, ok, "rule should successfully assert to a rollout segment") + assert.Len(t, rs.Segment.SegmentKeys, 2) + assert.Contains(t, rs.Segment.SegmentKeys, firstSegment.Key) + assert.Contains(t, rs.Segment.SegmentKeys, secondSegment.Key) + assert.NotZero(t, rollout.CreatedAt) + assert.NotZero(t, rollout.UpdatedAt) + } +} + func (s *DBTestSuite) TestListRolloutsNamespace() { t := s.T() @@ -441,13 +511,32 @@ func (s *DBTestSuite) TestUpdateRollout() { require.NoError(t, err) assert.NotNil(t, flag) + segmentOne, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: "segment_one", + Name: "Segment One", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + }) + + require.NoError(t, err) + assert.NotNil(t, segmentOne) + + segmentTwo, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: "segment_two", + Name: "Segment Two", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + }) + + require.NoError(t, err) + assert.NotNil(t, segmentTwo) + rollout, err := s.store.CreateRollout(context.TODO(), &flipt.CreateRolloutRequest{ FlagKey: flag.Key, Rank: 1, - Rule: &flipt.CreateRolloutRequest_Threshold{ - Threshold: &flipt.RolloutThreshold{ - Value: true, - Percentage: 40, + Rule: &flipt.CreateRolloutRequest_Segment{ + Segment: &flipt.RolloutSegment{ + Value: true, + SegmentKey: "segment_one", + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, }, }, }) @@ -459,20 +548,22 @@ func (s *DBTestSuite) TestUpdateRollout() { assert.Equal(t, storage.DefaultNamespace, rollout.NamespaceKey) assert.Equal(t, flag.Key, rollout.FlagKey) assert.Equal(t, int32(1), rollout.Rank) - assert.Equal(t, flipt.RolloutType_THRESHOLD_ROLLOUT_TYPE, rollout.Type) - assert.Equal(t, float32(40), rollout.GetThreshold().Percentage) - assert.Equal(t, true, rollout.GetThreshold().Value) + assert.Equal(t, flipt.RolloutType_SEGMENT_ROLLOUT_TYPE, rollout.Type) + assert.Equal(t, segmentOne.Key, rollout.GetSegment().SegmentKey) + assert.Equal(t, true, rollout.GetSegment().Value) assert.NotZero(t, rollout.CreatedAt) assert.Equal(t, rollout.CreatedAt.Seconds, rollout.UpdatedAt.Seconds) + assert.Equal(t, flipt.SegmentOperator_OR_SEGMENT_OPERATOR, rollout.GetSegment().SegmentOperator) updated, err := s.store.UpdateRollout(context.TODO(), &flipt.UpdateRolloutRequest{ Id: rollout.Id, FlagKey: rollout.FlagKey, Description: "foobar", - Rule: &flipt.UpdateRolloutRequest_Threshold{ - Threshold: &flipt.RolloutThreshold{ - Value: false, - Percentage: 80, + Rule: &flipt.UpdateRolloutRequest_Segment{ + Segment: &flipt.RolloutSegment{ + Value: false, + SegmentKeys: []string{segmentOne.Key, segmentTwo.Key}, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, }, }, }) @@ -484,9 +575,97 @@ func (s *DBTestSuite) TestUpdateRollout() { assert.Equal(t, rollout.FlagKey, updated.FlagKey) assert.Equal(t, "foobar", updated.Description) assert.Equal(t, int32(1), updated.Rank) - assert.Equal(t, flipt.RolloutType_THRESHOLD_ROLLOUT_TYPE, updated.Type) - assert.Equal(t, float32(80), updated.GetThreshold().Percentage) - assert.Equal(t, false, updated.GetThreshold().Value) + assert.Equal(t, flipt.RolloutType_SEGMENT_ROLLOUT_TYPE, updated.Type) + assert.Contains(t, updated.GetSegment().SegmentKeys, segmentOne.Key) + assert.Contains(t, updated.GetSegment().SegmentKeys, segmentTwo.Key) + assert.Equal(t, false, updated.GetSegment().Value) + assert.Equal(t, flipt.SegmentOperator_AND_SEGMENT_OPERATOR, updated.GetSegment().SegmentOperator) + assert.NotZero(t, updated.CreatedAt) + assert.NotZero(t, updated.UpdatedAt) +} + +func (s *DBTestSuite) TestUpdateRollout_OneSegment() { + t := s.T() + + flag, err := s.store.CreateFlag(context.TODO(), &flipt.CreateFlagRequest{ + Key: t.Name(), + Name: "foo", + Description: "bar", + Enabled: true, + }) + + require.NoError(t, err) + assert.NotNil(t, flag) + + segmentOne, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: fmt.Sprintf("one_%s", t.Name()), + Name: "Segment One", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + }) + + require.NoError(t, err) + assert.NotNil(t, segmentOne) + + segmentTwo, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: fmt.Sprintf("two_%s", t.Name()), + Name: "Segment Two", + MatchType: flipt.MatchType_ANY_MATCH_TYPE, + }) + + require.NoError(t, err) + assert.NotNil(t, segmentTwo) + + rollout, err := s.store.CreateRollout(context.TODO(), &flipt.CreateRolloutRequest{ + FlagKey: flag.Key, + Rank: 1, + Rule: &flipt.CreateRolloutRequest_Segment{ + Segment: &flipt.RolloutSegment{ + Value: true, + SegmentKeys: []string{segmentOne.Key, segmentTwo.Key}, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + }, + }, + }) + + require.NoError(t, err) + assert.NotNil(t, rollout) + + assert.NotZero(t, rollout.Id) + assert.Equal(t, storage.DefaultNamespace, rollout.NamespaceKey) + assert.Equal(t, flag.Key, rollout.FlagKey) + assert.Equal(t, int32(1), rollout.Rank) + assert.Equal(t, flipt.RolloutType_SEGMENT_ROLLOUT_TYPE, rollout.Type) + assert.Contains(t, rollout.GetSegment().SegmentKeys, segmentOne.Key) + assert.Contains(t, rollout.GetSegment().SegmentKeys, segmentTwo.Key) + assert.Equal(t, true, rollout.GetSegment().Value) + assert.NotZero(t, rollout.CreatedAt) + assert.Equal(t, rollout.CreatedAt.Seconds, rollout.UpdatedAt.Seconds) + assert.Equal(t, flipt.SegmentOperator_AND_SEGMENT_OPERATOR, rollout.GetSegment().SegmentOperator) + + updated, err := s.store.UpdateRollout(context.TODO(), &flipt.UpdateRolloutRequest{ + Id: rollout.Id, + FlagKey: rollout.FlagKey, + Description: "foobar", + Rule: &flipt.UpdateRolloutRequest_Segment{ + Segment: &flipt.RolloutSegment{ + Value: false, + SegmentKey: segmentOne.Key, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + }, + }, + }) + + require.NoError(t, err) + + assert.Equal(t, rollout.Id, updated.Id) + assert.Equal(t, storage.DefaultNamespace, updated.NamespaceKey) + assert.Equal(t, rollout.FlagKey, updated.FlagKey) + assert.Equal(t, "foobar", updated.Description) + assert.Equal(t, int32(1), updated.Rank) + assert.Equal(t, flipt.RolloutType_SEGMENT_ROLLOUT_TYPE, updated.Type) + assert.Equal(t, segmentOne.Key, updated.GetSegment().SegmentKey) + assert.Equal(t, false, updated.GetSegment().Value) + assert.Equal(t, flipt.SegmentOperator_OR_SEGMENT_OPERATOR, updated.GetSegment().SegmentOperator) assert.NotZero(t, updated.CreatedAt) assert.NotZero(t, updated.UpdatedAt) } @@ -587,8 +766,8 @@ func (s *DBTestSuite) TestUpdateRollout_InvalidType() { Rank: 1, Rule: &flipt.CreateRolloutRequest_Segment{ Segment: &flipt.RolloutSegment{ - SegmentKey: "segment_one", - Value: true, + SegmentKeys: []string{"segment_one"}, + Value: true, }, }, }) diff --git a/internal/storage/sql/rule_test.go b/internal/storage/sql/rule_test.go index ef4851c3bd..06ca88e951 100644 --- a/internal/storage/sql/rule_test.go +++ b/internal/storage/sql/rule_test.go @@ -72,6 +72,73 @@ func (s *DBTestSuite) TestGetRule() { assert.NotZero(t, got.UpdatedAt) } +func (s *DBTestSuite) TestGetRule_MultipleSegments() { + t := s.T() + + flag, err := s.store.CreateFlag(context.TODO(), &flipt.CreateFlagRequest{ + Key: t.Name(), + Name: "foo", + Description: "bar", + Enabled: true, + }) + + require.NoError(t, err) + assert.NotNil(t, flag) + + variant, err := s.store.CreateVariant(context.TODO(), &flipt.CreateVariantRequest{ + FlagKey: flag.Key, + Key: t.Name(), + Name: "foo", + Description: "bar", + }) + + require.NoError(t, err) + assert.NotNil(t, variant) + + firstSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: t.Name(), + Name: "foo", + Description: "bar", + }) + + require.NoError(t, err) + assert.NotNil(t, firstSegment) + + secondSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: "another_segment_1", + Name: "bar", + Description: "foo", + }) + + require.NoError(t, err) + assert.NotNil(t, secondSegment) + + rule, err := s.store.CreateRule(context.TODO(), &flipt.CreateRuleRequest{ + FlagKey: flag.Key, + SegmentKeys: []string{firstSegment.Key, secondSegment.Key}, + Rank: 1, + }) + + require.NoError(t, err) + assert.NotNil(t, rule) + + got, err := s.store.GetRule(context.TODO(), storage.DefaultNamespace, rule.Id) + + require.NoError(t, err) + assert.NotNil(t, got) + + assert.Equal(t, rule.Id, got.Id) + assert.Equal(t, storage.DefaultNamespace, got.NamespaceKey) + assert.Equal(t, rule.FlagKey, got.FlagKey) + + assert.Len(t, rule.SegmentKeys, 2) + assert.Equal(t, firstSegment.Key, rule.SegmentKeys[0]) + assert.Equal(t, secondSegment.Key, rule.SegmentKeys[1]) + assert.Equal(t, rule.Rank, got.Rank) + assert.NotZero(t, got.CreatedAt) + assert.NotZero(t, got.UpdatedAt) +} + func (s *DBTestSuite) TestGetRuleNamespace() { t := s.T() @@ -211,6 +278,84 @@ func (s *DBTestSuite) TestListRules() { } } +func (s *DBTestSuite) TestListRules_MultipleSegments() { + t := s.T() + + flag, err := s.store.CreateFlag(context.TODO(), &flipt.CreateFlagRequest{ + Key: t.Name(), + Name: "foo", + Description: "bar", + Enabled: true, + }) + + require.NoError(t, err) + assert.NotNil(t, flag) + + variant, err := s.store.CreateVariant(context.TODO(), &flipt.CreateVariantRequest{ + FlagKey: flag.Key, + Key: t.Name(), + Name: "foo", + Description: "bar", + }) + + require.NoError(t, err) + assert.NotNil(t, variant) + + firstSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: t.Name(), + Name: "foo", + Description: "bar", + }) + + require.NoError(t, err) + assert.NotNil(t, firstSegment) + + secondSegment, err := s.store.CreateSegment(context.TODO(), &flipt.CreateSegmentRequest{ + Key: "another_segment_2", + Name: "foo", + Description: "bar", + }) + + require.NoError(t, err) + assert.NotNil(t, secondSegment) + + reqs := []*flipt.CreateRuleRequest{ + { + FlagKey: flag.Key, + SegmentKeys: []string{firstSegment.Key, secondSegment.Key}, + Rank: 1, + }, + { + FlagKey: flag.Key, + SegmentKeys: []string{firstSegment.Key, secondSegment.Key}, + Rank: 2, + }, + } + + for _, req := range reqs { + _, err := s.store.CreateRule(context.TODO(), req) + require.NoError(t, err) + } + + _, err = s.store.ListRules(context.TODO(), storage.DefaultNamespace, flag.Key, storage.WithPageToken("Hello World")) + assert.EqualError(t, err, "pageToken is not valid: \"Hello World\"") + + res, err := s.store.ListRules(context.TODO(), storage.DefaultNamespace, flag.Key) + require.NoError(t, err) + + got := res.Results + assert.NotZero(t, len(got)) + + for _, rule := range got { + assert.Equal(t, storage.DefaultNamespace, rule.NamespaceKey) + assert.Len(t, rule.SegmentKeys, 2) + assert.Contains(t, rule.SegmentKeys, firstSegment.Key) + assert.Contains(t, rule.SegmentKeys, secondSegment.Key) + assert.NotZero(t, rule.CreatedAt) + assert.NotZero(t, rule.UpdatedAt) + } +} + func (s *DBTestSuite) TestListRulesNamespace() { t := s.T() @@ -615,10 +760,11 @@ func (s *DBTestSuite) TestCreateRuleAndDistributionNamespace() { assert.NotNil(t, segment) rule, err := s.store.CreateRule(context.TODO(), &flipt.CreateRuleRequest{ - NamespaceKey: s.namespace, - FlagKey: flag.Key, - SegmentKey: segment.Key, - Rank: 1, + NamespaceKey: s.namespace, + FlagKey: flag.Key, + SegmentKey: segment.Key, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + Rank: 1, }) require.NoError(t, err) @@ -631,6 +777,7 @@ func (s *DBTestSuite) TestCreateRuleAndDistributionNamespace() { assert.Equal(t, int32(1), rule.Rank) assert.NotZero(t, rule.CreatedAt) assert.Equal(t, rule.CreatedAt.Seconds, rule.UpdatedAt.Seconds) + assert.Equal(t, flipt.SegmentOperator_OR_SEGMENT_OPERATOR, rule.SegmentOperator) distribution, err := s.store.CreateDistribution(context.TODO(), &flipt.CreateDistributionRequest{ NamespaceKey: s.namespace, @@ -826,9 +973,10 @@ func (s *DBTestSuite) TestUpdateRuleAndDistribution() { assert.NotNil(t, segmentTwo) updatedRule, err := s.store.UpdateRule(context.TODO(), &flipt.UpdateRuleRequest{ - Id: rule.Id, - FlagKey: flag.Key, - SegmentKey: segmentTwo.Key, + Id: rule.Id, + FlagKey: flag.Key, + SegmentKey: segmentTwo.Key, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, }) require.NoError(t, err) @@ -838,9 +986,30 @@ func (s *DBTestSuite) TestUpdateRuleAndDistribution() { assert.Equal(t, rule.FlagKey, updatedRule.FlagKey) assert.Equal(t, segmentTwo.Key, updatedRule.SegmentKey) assert.Equal(t, int32(1), updatedRule.Rank) + assert.Equal(t, flipt.SegmentOperator_OR_SEGMENT_OPERATOR, updatedRule.SegmentOperator) // assert.Equal(t, rule.CreatedAt.Seconds, updatedRule.CreatedAt.Seconds) assert.NotZero(t, rule.UpdatedAt) + t.Log("Update rule to references two segments.") + + updatedRule, err = s.store.UpdateRule(context.TODO(), &flipt.UpdateRuleRequest{ + Id: rule.Id, + FlagKey: flag.Key, + SegmentKeys: []string{segmentOne.Key, segmentTwo.Key}, + SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR, + }) + + require.NoError(t, err) + assert.NotNil(t, updatedRule) + + assert.Equal(t, rule.Id, updatedRule.Id) + assert.Equal(t, rule.FlagKey, updatedRule.FlagKey) + assert.Contains(t, updatedRule.SegmentKeys, segmentOne.Key) + assert.Contains(t, updatedRule.SegmentKeys, segmentTwo.Key) + assert.Equal(t, flipt.SegmentOperator_AND_SEGMENT_OPERATOR, updatedRule.SegmentOperator) + assert.Equal(t, int32(1), updatedRule.Rank) + assert.NotZero(t, rule.UpdatedAt) + updatedDistribution, err := s.store.UpdateDistribution(context.TODO(), &flipt.UpdateDistributionRequest{ FlagKey: flag.Key, Id: distribution.Id, diff --git a/internal/storage/sql/sqlite/sqlite.go b/internal/storage/sql/sqlite/sqlite.go index 44d3ea6159..310b12cdf4 100644 --- a/internal/storage/sql/sqlite/sqlite.go +++ b/internal/storage/sql/sqlite/sqlite.go @@ -179,6 +179,22 @@ func (s *Store) CreateRule(ctx context.Context, r *flipt.CreateRuleRequest) (*fl return rule, nil } +func (s *Store) UpdateRule(ctx context.Context, r *flipt.UpdateRuleRequest) (*flipt.Rule, error) { + rule, err := s.Store.UpdateRule(ctx, r) + + if err != nil { + var serr sqlite3.Error + + if errors.As(err, &serr) && serr.Code == sqlite3.ErrConstraint { + return nil, errs.ErrNotFoundf(`rule "%s/%s"`, r.NamespaceKey, r.Id) + } + + return nil, err + } + + return rule, nil +} + func (s *Store) CreateDistribution(ctx context.Context, r *flipt.CreateDistributionRequest) (*flipt.Distribution, error) { dist, err := s.Store.CreateDistribution(ctx, r) diff --git a/internal/storage/storage.go b/internal/storage/storage.go index c6dd605624..0f7b5441bf 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -18,13 +18,18 @@ const ( // EvaluationRule represents a rule and constraints required for evaluating if a // given flagKey matches a segment type EvaluationRule struct { - ID string `json:"id"` - NamespaceKey string `json:"namespace_key,omitempty"` - FlagKey string `json:"flag_key,omitempty"` - SegmentKey string `json:"segment_key,omitempty"` - SegmentMatchType flipt.MatchType `json:"segment_match_type,omitempty"` - Rank int32 `json:"rank,omitempty"` - Constraints []EvaluationConstraint `json:"constraints,omitempty"` + ID string `json:"id"` + NamespaceKey string `json:"namespace_key,omitempty"` + FlagKey string `json:"flag_key,omitempty"` + Segments map[string]*EvaluationSegment `json:"segments,omitempty"` + Rank int32 `json:"rank,omitempty"` + SegmentOperator flipt.SegmentOperator `json:"segmentOperator,omitempty"` +} + +type EvaluationSegment struct { + SegmentKey string `json:"segment_key,omitempty"` + MatchType flipt.MatchType `json:"match_type,omitempty"` + Constraints []EvaluationConstraint `json:"constraints,omitempty"` } // EvaluationRollout represents a rollout in the form that helps with evaluation. @@ -44,10 +49,9 @@ type RolloutThreshold struct { // RolloutSegment represents Segment(s) for use in evaluation. type RolloutSegment struct { - Key string - MatchType flipt.MatchType - Value bool - Constraints []EvaluationConstraint + Value bool + SegmentOperator flipt.SegmentOperator + Segments map[string]*EvaluationSegment } // EvaluationConstraint represents a segment constraint that is used for evaluation diff --git a/rpc/flipt/flipt.pb.go b/rpc/flipt/flipt.pb.go index 1f4139be32..29b18f1abd 100644 --- a/rpc/flipt/flipt.pb.go +++ b/rpc/flipt/flipt.pb.go @@ -273,6 +273,52 @@ func (RolloutType) EnumDescriptor() ([]byte, []int) { return file_flipt_proto_rawDescGZIP(), []int{4} } +type SegmentOperator int32 + +const ( + SegmentOperator_OR_SEGMENT_OPERATOR SegmentOperator = 0 + SegmentOperator_AND_SEGMENT_OPERATOR SegmentOperator = 1 +) + +// Enum value maps for SegmentOperator. +var ( + SegmentOperator_name = map[int32]string{ + 0: "OR_SEGMENT_OPERATOR", + 1: "AND_SEGMENT_OPERATOR", + } + SegmentOperator_value = map[string]int32{ + "OR_SEGMENT_OPERATOR": 0, + "AND_SEGMENT_OPERATOR": 1, + } +) + +func (x SegmentOperator) Enum() *SegmentOperator { + p := new(SegmentOperator) + *p = x + return p +} + +func (x SegmentOperator) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SegmentOperator) Descriptor() protoreflect.EnumDescriptor { + return file_flipt_proto_enumTypes[5].Descriptor() +} + +func (SegmentOperator) Type() protoreflect.EnumType { + return &file_flipt_proto_enumTypes[5] +} + +func (x SegmentOperator) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SegmentOperator.Descriptor instead. +func (SegmentOperator) EnumDescriptor() ([]byte, []int) { + return file_flipt_proto_rawDescGZIP(), []int{5} +} + type EvaluationRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -491,11 +537,12 @@ type EvaluationResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` - EntityId string `protobuf:"bytes,2,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"` - RequestContext map[string]string `protobuf:"bytes,3,rep,name=request_context,json=requestContext,proto3" json:"request_context,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Match bool `protobuf:"varint,4,opt,name=match,proto3" json:"match,omitempty"` - FlagKey string `protobuf:"bytes,5,opt,name=flag_key,json=flagKey,proto3" json:"flag_key,omitempty"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + EntityId string `protobuf:"bytes,2,opt,name=entity_id,json=entityId,proto3" json:"entity_id,omitempty"` + RequestContext map[string]string `protobuf:"bytes,3,rep,name=request_context,json=requestContext,proto3" json:"request_context,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Match bool `protobuf:"varint,4,opt,name=match,proto3" json:"match,omitempty"` + FlagKey string `protobuf:"bytes,5,opt,name=flag_key,json=flagKey,proto3" json:"flag_key,omitempty"` + // Deprecated: Marked as deprecated in flipt.proto. SegmentKey string `protobuf:"bytes,6,opt,name=segment_key,json=segmentKey,proto3" json:"segment_key,omitempty"` Timestamp *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Value string `protobuf:"bytes,8,opt,name=value,proto3" json:"value,omitempty"` @@ -503,6 +550,7 @@ type EvaluationResponse struct { Attachment string `protobuf:"bytes,10,opt,name=attachment,proto3" json:"attachment,omitempty"` Reason EvaluationReason `protobuf:"varint,11,opt,name=reason,proto3,enum=flipt.EvaluationReason" json:"reason,omitempty"` NamespaceKey string `protobuf:"bytes,12,opt,name=namespace_key,json=namespaceKey,proto3" json:"namespace_key,omitempty"` + SegmentKeys []string `protobuf:"bytes,13,rep,name=segment_keys,json=segmentKeys,proto3" json:"segment_keys,omitempty"` } func (x *EvaluationResponse) Reset() { @@ -572,6 +620,7 @@ func (x *EvaluationResponse) GetFlagKey() string { return "" } +// Deprecated: Marked as deprecated in flipt.proto. func (x *EvaluationResponse) GetSegmentKey() string { if x != nil { return x.SegmentKey @@ -621,6 +670,13 @@ func (x *EvaluationResponse) GetNamespaceKey() string { return "" } +func (x *EvaluationResponse) GetSegmentKeys() []string { + if x != nil { + return x.SegmentKeys + } + return nil +} + type Namespace struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2972,8 +3028,11 @@ type RolloutSegment struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - SegmentKey string `protobuf:"bytes,1,opt,name=segment_key,json=segmentKey,proto3" json:"segment_key,omitempty"` - Value bool `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` + // Deprecated: Marked as deprecated in flipt.proto. + SegmentKey string `protobuf:"bytes,1,opt,name=segment_key,json=segmentKey,proto3" json:"segment_key,omitempty"` + Value bool `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"` + SegmentKeys []string `protobuf:"bytes,3,rep,name=segment_keys,json=segmentKeys,proto3" json:"segment_keys,omitempty"` + SegmentOperator SegmentOperator `protobuf:"varint,4,opt,name=segment_operator,json=segmentOperator,proto3,enum=flipt.SegmentOperator" json:"segment_operator,omitempty"` } func (x *RolloutSegment) Reset() { @@ -3008,6 +3067,7 @@ func (*RolloutSegment) Descriptor() ([]byte, []int) { return file_flipt_proto_rawDescGZIP(), []int{34} } +// Deprecated: Marked as deprecated in flipt.proto. func (x *RolloutSegment) GetSegmentKey() string { if x != nil { return x.SegmentKey @@ -3022,6 +3082,20 @@ func (x *RolloutSegment) GetValue() bool { return false } +func (x *RolloutSegment) GetSegmentKeys() []string { + if x != nil { + return x.SegmentKeys + } + return nil +} + +func (x *RolloutSegment) GetSegmentOperator() SegmentOperator { + if x != nil { + return x.SegmentOperator + } + return SegmentOperator_OR_SEGMENT_OPERATOR +} + type RolloutThreshold struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3631,14 +3705,16 @@ type Rule struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - FlagKey string `protobuf:"bytes,2,opt,name=flag_key,json=flagKey,proto3" json:"flag_key,omitempty"` - SegmentKey string `protobuf:"bytes,3,opt,name=segment_key,json=segmentKey,proto3" json:"segment_key,omitempty"` - Distributions []*Distribution `protobuf:"bytes,4,rep,name=distributions,proto3" json:"distributions,omitempty"` - Rank int32 `protobuf:"varint,5,opt,name=rank,proto3" json:"rank,omitempty"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` - NamespaceKey string `protobuf:"bytes,8,opt,name=namespace_key,json=namespaceKey,proto3" json:"namespace_key,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + FlagKey string `protobuf:"bytes,2,opt,name=flag_key,json=flagKey,proto3" json:"flag_key,omitempty"` + SegmentKey string `protobuf:"bytes,3,opt,name=segment_key,json=segmentKey,proto3" json:"segment_key,omitempty"` + Distributions []*Distribution `protobuf:"bytes,4,rep,name=distributions,proto3" json:"distributions,omitempty"` + Rank int32 `protobuf:"varint,5,opt,name=rank,proto3" json:"rank,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + NamespaceKey string `protobuf:"bytes,8,opt,name=namespace_key,json=namespaceKey,proto3" json:"namespace_key,omitempty"` + SegmentKeys []string `protobuf:"bytes,9,rep,name=segment_keys,json=segmentKeys,proto3" json:"segment_keys,omitempty"` + SegmentOperator SegmentOperator `protobuf:"varint,10,opt,name=segment_operator,json=segmentOperator,proto3,enum=flipt.SegmentOperator" json:"segment_operator,omitempty"` } func (x *Rule) Reset() { @@ -3729,6 +3805,20 @@ func (x *Rule) GetNamespaceKey() string { return "" } +func (x *Rule) GetSegmentKeys() []string { + if x != nil { + return x.SegmentKeys + } + return nil +} + +func (x *Rule) GetSegmentOperator() SegmentOperator { + if x != nil { + return x.SegmentOperator + } + return SegmentOperator_OR_SEGMENT_OPERATOR +} + type RuleList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3941,10 +4031,13 @@ type CreateRuleRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - FlagKey string `protobuf:"bytes,1,opt,name=flag_key,json=flagKey,proto3" json:"flag_key,omitempty"` - SegmentKey string `protobuf:"bytes,2,opt,name=segment_key,json=segmentKey,proto3" json:"segment_key,omitempty"` - Rank int32 `protobuf:"varint,3,opt,name=rank,proto3" json:"rank,omitempty"` - NamespaceKey string `protobuf:"bytes,4,opt,name=namespace_key,json=namespaceKey,proto3" json:"namespace_key,omitempty"` + FlagKey string `protobuf:"bytes,1,opt,name=flag_key,json=flagKey,proto3" json:"flag_key,omitempty"` + // Deprecated: Marked as deprecated in flipt.proto. + SegmentKey string `protobuf:"bytes,2,opt,name=segment_key,json=segmentKey,proto3" json:"segment_key,omitempty"` + Rank int32 `protobuf:"varint,3,opt,name=rank,proto3" json:"rank,omitempty"` + NamespaceKey string `protobuf:"bytes,4,opt,name=namespace_key,json=namespaceKey,proto3" json:"namespace_key,omitempty"` + SegmentKeys []string `protobuf:"bytes,5,rep,name=segment_keys,json=segmentKeys,proto3" json:"segment_keys,omitempty"` + SegmentOperator SegmentOperator `protobuf:"varint,6,opt,name=segment_operator,json=segmentOperator,proto3,enum=flipt.SegmentOperator" json:"segment_operator,omitempty"` } func (x *CreateRuleRequest) Reset() { @@ -3986,6 +4079,7 @@ func (x *CreateRuleRequest) GetFlagKey() string { return "" } +// Deprecated: Marked as deprecated in flipt.proto. func (x *CreateRuleRequest) GetSegmentKey() string { if x != nil { return x.SegmentKey @@ -4007,15 +4101,32 @@ func (x *CreateRuleRequest) GetNamespaceKey() string { return "" } +func (x *CreateRuleRequest) GetSegmentKeys() []string { + if x != nil { + return x.SegmentKeys + } + return nil +} + +func (x *CreateRuleRequest) GetSegmentOperator() SegmentOperator { + if x != nil { + return x.SegmentOperator + } + return SegmentOperator_OR_SEGMENT_OPERATOR +} + type UpdateRuleRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - FlagKey string `protobuf:"bytes,2,opt,name=flag_key,json=flagKey,proto3" json:"flag_key,omitempty"` - SegmentKey string `protobuf:"bytes,3,opt,name=segment_key,json=segmentKey,proto3" json:"segment_key,omitempty"` - NamespaceKey string `protobuf:"bytes,4,opt,name=namespace_key,json=namespaceKey,proto3" json:"namespace_key,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + FlagKey string `protobuf:"bytes,2,opt,name=flag_key,json=flagKey,proto3" json:"flag_key,omitempty"` + // Deprecated: Marked as deprecated in flipt.proto. + SegmentKey string `protobuf:"bytes,3,opt,name=segment_key,json=segmentKey,proto3" json:"segment_key,omitempty"` + NamespaceKey string `protobuf:"bytes,4,opt,name=namespace_key,json=namespaceKey,proto3" json:"namespace_key,omitempty"` + SegmentKeys []string `protobuf:"bytes,5,rep,name=segment_keys,json=segmentKeys,proto3" json:"segment_keys,omitempty"` + SegmentOperator SegmentOperator `protobuf:"varint,6,opt,name=segment_operator,json=segmentOperator,proto3,enum=flipt.SegmentOperator" json:"segment_operator,omitempty"` } func (x *UpdateRuleRequest) Reset() { @@ -4064,6 +4175,7 @@ func (x *UpdateRuleRequest) GetFlagKey() string { return "" } +// Deprecated: Marked as deprecated in flipt.proto. func (x *UpdateRuleRequest) GetSegmentKey() string { if x != nil { return x.SegmentKey @@ -4078,6 +4190,20 @@ func (x *UpdateRuleRequest) GetNamespaceKey() string { return "" } +func (x *UpdateRuleRequest) GetSegmentKeys() []string { + if x != nil { + return x.SegmentKeys + } + return nil +} + +func (x *UpdateRuleRequest) GetSegmentOperator() SegmentOperator { + if x != nil { + return x.SegmentOperator + } + return SegmentOperator_OR_SEGMENT_OPERATOR +} + type DeleteRuleRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -4583,7 +4709,7 @@ var file_flipt_proto_rawDesc = []byte{ 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x36, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x15, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x22, 0xbb, + 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x22, 0xe2, 0x04, 0x0a, 0x12, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, @@ -4597,252 +4723,243 @@ var file_flipt_proto_rawDesc = []byte{ 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, - 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x38, 0x0a, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x08, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x01, 0x52, 0x15, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x69, 0x6c, - 0x6c, 0x69, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, - 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, - 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x45, 0x76, 0x61, 0x6c, - 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, - 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x1a, 0x41, 0x0a, 0x13, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xe7, 0x01, 0x0a, - 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x8a, 0x01, 0x0a, 0x0d, 0x4e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x0a, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x0a, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, - 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x22, 0x27, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x67, 0x0a, 0x14, - 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x66, - 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, - 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x60, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x60, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0b, 0x73, 0x65, + 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, + 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, + 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x36, 0x0a, 0x17, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x01, + 0x52, 0x15, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, + 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x2f, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, + 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, + 0x0c, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x0d, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x73, + 0x1a, 0x41, 0x0a, 0x13, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65, + 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0xe7, 0x01, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2a, 0x0a, 0x16, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xd4, 0x02, 0x0a, 0x04, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2a, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, - 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, - 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, - 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, - 0x61, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x76, 0x0a, 0x08, - 0x46, 0x6c, 0x61, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, - 0x46, 0x6c, 0x61, 0x67, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, - 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x47, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x52, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f, + 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x70, 0x72, + 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x8a, 0x01, + 0x0a, 0x0d, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, + 0x30, 0x0a, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, + 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x27, 0x0a, 0x13, 0x47, 0x65, + 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x22, 0x67, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1d, 0x0a, + 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x60, 0x0a, 0x16, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x87, 0x01, - 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xbf, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x60, + 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x2a, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0xd4, 0x02, 0x0a, + 0x04, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, + 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2a, 0x0a, + 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, + 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x23, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x76, 0x0a, 0x08, 0x46, 0x6c, 0x61, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, + 0x21, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x05, 0x66, 0x6c, 0x61, + 0x67, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x47, 0x0a, 0x0e, 0x47, + 0x65, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x9a, 0x01, 0x0a, 0x11, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x4a, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, - 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, - 0x65, 0x79, 0x22, 0xb7, 0x02, 0x0a, 0x07, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x22, 0x87, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6c, 0x61, + 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1a, + 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x02, + 0x18, 0x01, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xbf, + 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, + 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x66, 0x6c, 0x69, 0x70, + 0x74, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x22, 0x9a, 0x01, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, + 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x4a, 0x0a, + 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xb7, 0x02, 0x0a, 0x07, 0x56, 0x61, + 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, + 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x23, + 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x4b, 0x65, 0x79, 0x22, 0xbe, 0x01, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x56, 0x61, + 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x22, 0xce, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, + 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, + 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, + 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x66, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x56, + 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, + 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xd2, 0x02, + 0x0a, 0x07, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, 0x63, - 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, - 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xbe, 0x01, 0x0a, - 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x61, - 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, - 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xce, 0x01, - 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, - 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, - 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x66, - 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, - 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xd2, 0x02, 0x0a, 0x07, 0x53, 0x65, 0x67, 0x6d, 0x65, - 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x12, 0x33, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x18, - 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, - 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, - 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x66, 0x6c, 0x69, 0x70, - 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x6d, 0x61, 0x74, - 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x82, 0x01, 0x0a, 0x0b, - 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x08, 0x73, - 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x73, - 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, - 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, - 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, - 0x22, 0x4a, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x8a, 0x01, 0x0a, - 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x66, 0x66, - 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xb4, 0x01, 0x0a, 0x14, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x0a, 0x6d, 0x61, - 0x74, 0x63, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, - 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x09, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x33, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, + 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, + 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x2f, 0x0a, 0x0a, + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x10, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x09, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, + 0x65, 0x79, 0x22, 0x82, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x69, + 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x08, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x08, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x26, + 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x4a, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, 0x65, + 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x23, + 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x4b, 0x65, 0x79, 0x22, 0x8a, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, + 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, - 0x22, 0xb4, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, + 0x22, 0xb4, 0x01, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, @@ -4853,496 +4970,538 @@ var file_flipt_proto_rawDesc = []byte{ 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x4d, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xf3, 0x02, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x73, 0x74, - 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, - 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, 0x6d, - 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x1a, 0x0a, - 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xfa, 0x01, 0x0a, - 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, - 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, - 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, - 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, - 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x8a, 0x02, 0x0a, 0x17, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, - 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, 0x6d, - 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x1a, 0x0a, - 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6f, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, - 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xa1, 0x03, 0x0a, 0x07, 0x52, 0x6f, 0x6c, 0x6c, - 0x6f, 0x75, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, - 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x12, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, - 0x61, 0x6e, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x12, - 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xb4, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x0a, 0x6d, 0x61, 0x74, 0x63, + 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, + 0x6d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x4d, + 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xf3, 0x02, + 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x66, 0x6c, + 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x70, + 0x65, 0x72, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, + 0x65, 0x72, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x31, 0x0a, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, - 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, - 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x37, 0x0a, 0x09, 0x74, 0x68, - 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x54, 0x68, 0x72, - 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, - 0x6f, 0x6c, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, 0x52, - 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1f, 0x0a, - 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x22, 0x48, 0x0a, 0x10, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x54, - 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, 0x72, 0x63, - 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, 0x70, 0x65, - 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x7c, - 0x0a, 0x0b, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, - 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x05, 0x72, 0x75, - 0x6c, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x89, 0x01, 0x0a, - 0x12, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, - 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, - 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, - 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x63, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x52, - 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, - 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, - 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, - 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x22, 0x80, 0x02, - 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x66, - 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, - 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x07, - 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x67, - 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x37, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x15, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, - 0x75, 0x74, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x09, 0x74, - 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, - 0x22, 0xfc, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, + 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x23, 0x0a, 0x0d, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, + 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0xfa, 0x01, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, + 0x12, 0x29, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, + 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x20, + 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x8a, 0x02, 0x0a, 0x17, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, + 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x29, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x66, 0x6c, + 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x70, + 0x65, 0x72, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x70, + 0x65, 0x72, 0x74, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6f, 0x0a, + 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, + 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xa1, + 0x03, 0x0a, 0x07, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, + 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, + 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x31, + 0x0a, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x53, + 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x37, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x15, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, + 0x6c, 0x6f, 0x75, 0x74, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x48, 0x00, 0x52, + 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x72, 0x75, + 0x6c, 0x65, 0x22, 0xb1, 0x01, 0x0a, 0x0e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x53, 0x65, + 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, + 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, + 0x65, 0x79, 0x73, 0x12, 0x41, 0x0a, 0x10, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0f, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x48, 0x0a, 0x10, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, + 0x74, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x70, 0x65, + 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0a, + 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x22, 0x7c, 0x0a, 0x0b, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, + 0x24, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x05, + 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, + 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x89, + 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, + 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, + 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x63, 0x0a, 0x11, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x22, + 0x80, 0x02, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, + 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x20, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, + 0x0a, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x53, + 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x37, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x15, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, + 0x6c, 0x6f, 0x75, 0x74, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x48, 0x00, 0x52, + 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x72, 0x75, + 0x6c, 0x65, 0x22, 0xfc, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, + 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, + 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, + 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x53, 0x65, + 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, + 0x12, 0x37, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x15, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, + 0x6f, 0x75, 0x74, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x09, + 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x72, 0x75, 0x6c, + 0x65, 0x22, 0x66, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x07, 0x73, - 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x67, 0x6d, - 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x07, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x37, - 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, - 0x74, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x48, 0x00, 0x52, 0x09, 0x74, 0x68, - 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x22, - 0x66, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, - 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x22, 0x77, 0x0a, 0x14, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, - 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x49, 0x64, 0x73, - 0x22, 0xbc, 0x02, 0x0a, 0x04, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, - 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, - 0x67, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, - 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0d, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, - 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x22, 0x77, 0x0a, 0x14, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, + 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x5f, 0x69, 0x64, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x49, + 0x64, 0x73, 0x22, 0xa2, 0x03, 0x0a, 0x04, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, + 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, + 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, + 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x39, 0x0a, 0x0d, 0x64, 0x69, 0x73, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x23, 0x0a, 0x0d, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, + 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, + 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, + 0x4b, 0x65, 0x79, 0x73, 0x12, 0x41, 0x0a, 0x10, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0f, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x76, 0x0a, 0x08, 0x52, 0x75, 0x6c, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x52, + 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, + 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, + 0xa2, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x66, 0x66, + 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, + 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x22, 0x60, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, + 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xf2, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, + 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, + 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, + 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x72, 0x61, 0x6e, 0x6b, + 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x67, + 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x41, 0x0a, 0x10, 0x73, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, + 0x6e, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0f, 0x73, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x22, 0xee, 0x01, 0x0a, 0x11, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0b, + 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, + 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, + 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x41, 0x0a, 0x10, 0x73, 0x65, 0x67, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0f, 0x73, 0x65, 0x67, + 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x63, 0x0a, 0x11, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, + 0x79, 0x22, 0x6e, 0x0a, 0x11, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, + 0x79, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x73, 0x12, 0x23, 0x0a, 0x0d, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, + 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x76, + 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x6f, + 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, 0x72, 0x6f, 0x6c, + 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, + 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, - 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, + 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, - 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, - 0x76, 0x0a, 0x08, 0x52, 0x75, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x05, 0x72, - 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x26, - 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xa2, 0x01, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x05, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x19, 0x0a, - 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x60, 0x0a, 0x0e, - 0x47, 0x65, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, - 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x88, - 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, - 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, - 0x12, 0x12, 0x0a, 0x04, 0x72, 0x61, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, - 0x72, 0x61, 0x6e, 0x6b, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x84, 0x01, 0x0a, 0x11, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, - 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, - 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xad, 0x01, 0x0a, 0x19, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, + 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x72, + 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, 0x72, 0x6f, + 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xbd, 0x01, 0x0a, 0x19, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, + 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x72, + 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x02, 0x52, 0x07, 0x72, 0x6f, + 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xa3, 0x01, 0x0a, 0x19, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, + 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, - 0x22, 0x63, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, - 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0x6e, 0x0a, 0x11, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x75, - 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x6c, - 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, 0x6c, - 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x73, - 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xe6, 0x01, 0x0a, 0x0c, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, - 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x18, - 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, - 0x07, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xad, - 0x01, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, - 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, - 0x18, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, - 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xbd, - 0x01, 0x0a, 0x19, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, - 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, - 0x18, 0x0a, 0x07, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x02, - 0x52, 0x07, 0x72, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xa3, - 0x01, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, - 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x66, 0x6c, 0x61, 0x67, 0x4b, 0x65, 0x79, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, - 0x23, 0x0a, 0x0d, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6b, 0x65, 0x79, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x4b, 0x65, 0x79, 0x2a, 0xb6, 0x01, 0x0a, 0x10, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x4b, - 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x56, 0x41, 0x4c, 0x55, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, - 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x23, 0x0a, 0x1f, 0x46, 0x4c, 0x41, 0x47, - 0x5f, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x56, 0x41, 0x4c, 0x55, 0x41, - 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x24, 0x0a, - 0x20, 0x46, 0x4c, 0x41, 0x47, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x5f, - 0x45, 0x56, 0x41, 0x4c, 0x55, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, - 0x4e, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x45, 0x56, 0x41, - 0x4c, 0x55, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x10, 0x03, - 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x45, 0x56, 0x41, 0x4c, 0x55, 0x41, - 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x10, 0x04, 0x2a, 0x38, 0x0a, - 0x08, 0x46, 0x6c, 0x61, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x41, 0x52, - 0x49, 0x41, 0x4e, 0x54, 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, - 0x12, 0x15, 0x0a, 0x11, 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x5f, 0x46, 0x4c, 0x41, 0x47, - 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x01, 0x2a, 0x33, 0x0a, 0x09, 0x4d, 0x61, 0x74, 0x63, 0x68, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x4c, 0x4c, 0x5f, 0x4d, 0x41, 0x54, 0x43, - 0x48, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x4e, 0x59, 0x5f, - 0x4d, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x01, 0x2a, 0xa0, 0x01, 0x0a, - 0x0e, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x1b, 0x0a, 0x17, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, - 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, - 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, - 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x4e, 0x55, 0x4d, 0x42, - 0x45, 0x52, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x59, - 0x50, 0x45, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x5f, - 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, - 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x44, 0x41, 0x54, 0x45, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x43, 0x4f, - 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x04, 0x2a, - 0x5d, 0x0a, 0x0b, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, - 0x0a, 0x14, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x52, 0x4f, 0x4c, 0x4c, 0x4f, 0x55, - 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x45, 0x47, 0x4d, - 0x45, 0x4e, 0x54, 0x5f, 0x52, 0x4f, 0x4c, 0x4c, 0x4f, 0x55, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, - 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x48, 0x52, 0x45, 0x53, 0x48, 0x4f, 0x4c, 0x44, 0x5f, - 0x52, 0x4f, 0x4c, 0x4c, 0x4f, 0x55, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x02, 0x32, 0xd5, - 0x13, 0x0a, 0x05, 0x46, 0x6c, 0x69, 0x70, 0x74, 0x12, 0x41, 0x0a, 0x08, 0x45, 0x76, 0x61, 0x6c, - 0x75, 0x61, 0x74, 0x65, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x45, 0x76, 0x61, - 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, - 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0d, 0x42, - 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3e, 0x0a, - 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1a, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x66, 0x6c, 0x69, 0x70, - 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, - 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, - 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4c, 0x69, - 0x73, 0x74, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, - 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x00, - 0x12, 0x4a, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x07, - 0x47, 0x65, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x15, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, - 0x47, 0x65, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, - 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x22, 0x00, 0x12, 0x36, 0x0a, - 0x09, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x4c, - 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, - 0x6c, 0x61, 0x67, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x0a, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, - 0x67, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, 0x6c, 0x61, - 0x67, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x56, - 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x56, 0x61, 0x72, 0x69, - 0x61, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, - 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x56, 0x61, 0x72, 0x69, - 0x61, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x56, - 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2f, 0x0a, - 0x07, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x15, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x00, 0x12, 0x36, - 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, - 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x52, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, - 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x00, 0x12, 0x35, 0x0a, - 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x2e, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x75, - 0x6c, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0a, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x75, 0x6c, - 0x65, 0x73, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2a, 0xb6, 0x01, 0x0a, 0x10, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x19, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x5f, 0x45, 0x56, 0x41, 0x4c, 0x55, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x10, 0x00, 0x12, 0x23, 0x0a, 0x1f, 0x46, 0x4c, 0x41, 0x47, 0x5f, 0x44, 0x49, 0x53, + 0x41, 0x42, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x56, 0x41, 0x4c, 0x55, 0x41, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x24, 0x0a, 0x20, 0x46, 0x4c, 0x41, + 0x47, 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x45, 0x56, 0x41, 0x4c, + 0x55, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x10, 0x02, 0x12, + 0x1b, 0x0a, 0x17, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x45, 0x56, 0x41, 0x4c, 0x55, 0x41, 0x54, + 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x10, 0x03, 0x12, 0x1b, 0x0a, 0x17, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x45, 0x56, 0x41, 0x4c, 0x55, 0x41, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x10, 0x04, 0x2a, 0x38, 0x0a, 0x08, 0x46, 0x6c, 0x61, + 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x56, 0x41, 0x52, 0x49, 0x41, 0x4e, 0x54, + 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, + 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x5f, 0x46, 0x4c, 0x41, 0x47, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x10, 0x01, 0x2a, 0x33, 0x0a, 0x09, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x4c, 0x4c, 0x5f, 0x4d, 0x41, 0x54, 0x43, 0x48, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x41, 0x4e, 0x59, 0x5f, 0x4d, 0x41, 0x54, 0x43, + 0x48, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x01, 0x2a, 0xa0, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, + 0x70, 0x61, 0x72, 0x69, 0x73, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, + 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x54, 0x52, 0x49, + 0x4e, 0x47, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x5f, 0x43, + 0x4f, 0x4d, 0x50, 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x02, + 0x12, 0x1b, 0x0a, 0x17, 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x5f, 0x43, 0x4f, 0x4d, 0x50, + 0x41, 0x52, 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x03, 0x12, 0x1c, 0x0a, + 0x18, 0x44, 0x41, 0x54, 0x45, 0x54, 0x49, 0x4d, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x50, 0x41, 0x52, + 0x49, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x04, 0x2a, 0x5d, 0x0a, 0x0b, 0x52, + 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x52, 0x4f, 0x4c, 0x4c, 0x4f, 0x55, 0x54, 0x5f, 0x54, 0x59, + 0x50, 0x45, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x45, 0x47, 0x4d, 0x45, 0x4e, 0x54, 0x5f, + 0x52, 0x4f, 0x4c, 0x4c, 0x4f, 0x55, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x01, 0x12, 0x1a, + 0x0a, 0x16, 0x54, 0x48, 0x52, 0x45, 0x53, 0x48, 0x4f, 0x4c, 0x44, 0x5f, 0x52, 0x4f, 0x4c, 0x4c, + 0x4f, 0x55, 0x54, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x10, 0x02, 0x2a, 0x44, 0x0a, 0x0f, 0x53, 0x65, + 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x0a, + 0x13, 0x4f, 0x52, 0x5f, 0x53, 0x45, 0x47, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x52, + 0x41, 0x54, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x41, 0x4e, 0x44, 0x5f, 0x53, 0x45, + 0x47, 0x4d, 0x45, 0x4e, 0x54, 0x5f, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x4f, 0x52, 0x10, 0x01, + 0x32, 0xd5, 0x13, 0x0a, 0x05, 0x46, 0x6c, 0x69, 0x70, 0x74, 0x12, 0x41, 0x0a, 0x08, 0x45, 0x76, + 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x45, + 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x19, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x50, 0x0a, + 0x0d, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x12, 0x1d, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x61, 0x6c, + 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x61, 0x6c, 0x75, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x3e, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x1a, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x66, 0x6c, + 0x69, 0x70, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x00, 0x12, + 0x45, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x73, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x66, 0x6c, 0x69, 0x70, + 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, + 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x1d, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1d, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2f, + 0x0a, 0x07, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x15, 0x2e, 0x66, 0x6c, 0x69, 0x70, + 0x74, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x22, 0x00, 0x12, + 0x36, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, + 0x67, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x22, 0x00, 0x12, 0x35, + 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x12, 0x18, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x46, + 0x6c, 0x61, 0x67, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x46, + 0x6c, 0x61, 0x67, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x46, 0x6c, 0x61, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x56, 0x61, + 0x72, 0x69, 0x61, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x56, 0x61, + 0x72, 0x69, 0x61, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, + 0x2f, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x15, 0x2e, 0x66, 0x6c, 0x69, + 0x70, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x00, + 0x12, 0x36, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x75, + 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x00, 0x12, + 0x35, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, + 0x52, 0x75, 0x6c, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0a, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, + 0x75, 0x6c, 0x65, 0x73, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x52, - 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x47, - 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, - 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, - 0x74, 0x73, 0x12, 0x19, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x4c, 0x69, 0x73, - 0x74, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, - 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, - 0x74, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, - 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, - 0x74, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, - 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0d, 0x4f, - 0x72, 0x64, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x73, 0x12, 0x1b, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0a, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, + 0x75, 0x74, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x6f, 0x6c, 0x6c, + 0x6f, 0x75, 0x74, 0x73, 0x12, 0x19, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x12, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x4c, + 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, + 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, + 0x6f, 0x75, 0x74, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x52, 0x6f, 0x6c, 0x6c, + 0x6f, 0x75, 0x74, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, + 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x46, 0x0a, + 0x0d, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x6c, 0x6f, 0x75, 0x74, 0x73, 0x12, 0x1b, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x6f, 0x6c, 0x6c, + 0x6f, 0x75, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, + 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x66, 0x6c, + 0x69, 0x70, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x69, + 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x66, 0x6c, 0x69, + 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, - 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, - 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x00, 0x12, 0x50, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, - 0x74, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x67, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, 0x6c, - 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x3f, 0x0a, - 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x19, 0x2e, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, - 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x3e, - 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, - 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x3e, - 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, - 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x46, - 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, - 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, - 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x22, 0x00, 0x12, - 0x47, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, - 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x73, - 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x53, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x18, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x53, + 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, + 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, + 0x3f, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, + 0x19, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x66, 0x6c, 0x69, + 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, + 0x12, 0x3e, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x00, + 0x12, 0x3e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x00, + 0x12, 0x46, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x1b, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x53, 0x65, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x66, - 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, - 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x1d, 0x5a, 0x1b, 0x67, 0x6f, 0x2e, 0x66, 0x6c, 0x69, - 0x70, 0x74, 0x2e, 0x69, 0x6f, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x70, 0x63, 0x2f, - 0x66, 0x6c, 0x69, 0x70, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, + 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x22, + 0x00, 0x12, 0x47, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, + 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x43, 0x6f, + 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x10, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x12, 0x1e, + 0x2e, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, + 0x73, 0x74, 0x72, 0x61, 0x69, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x1d, 0x5a, 0x1b, 0x67, 0x6f, 0x2e, 0x66, + 0x6c, 0x69, 0x70, 0x74, 0x2e, 0x69, 0x6f, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x70, + 0x63, 0x2f, 0x66, 0x6c, 0x69, 0x70, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -5357,7 +5516,7 @@ func file_flipt_proto_rawDescGZIP() []byte { return file_flipt_proto_rawDescData } -var file_flipt_proto_enumTypes = make([]protoimpl.EnumInfo, 5) +var file_flipt_proto_enumTypes = make([]protoimpl.EnumInfo, 6) var file_flipt_proto_msgTypes = make([]protoimpl.MessageInfo, 57) var file_flipt_proto_goTypes = []interface{}{ (EvaluationReason)(0), // 0: flipt.EvaluationReason @@ -5365,193 +5524,198 @@ var file_flipt_proto_goTypes = []interface{}{ (MatchType)(0), // 2: flipt.MatchType (ComparisonType)(0), // 3: flipt.ComparisonType (RolloutType)(0), // 4: flipt.RolloutType - (*EvaluationRequest)(nil), // 5: flipt.EvaluationRequest - (*BatchEvaluationRequest)(nil), // 6: flipt.BatchEvaluationRequest - (*BatchEvaluationResponse)(nil), // 7: flipt.BatchEvaluationResponse - (*EvaluationResponse)(nil), // 8: flipt.EvaluationResponse - (*Namespace)(nil), // 9: flipt.Namespace - (*NamespaceList)(nil), // 10: flipt.NamespaceList - (*GetNamespaceRequest)(nil), // 11: flipt.GetNamespaceRequest - (*ListNamespaceRequest)(nil), // 12: flipt.ListNamespaceRequest - (*CreateNamespaceRequest)(nil), // 13: flipt.CreateNamespaceRequest - (*UpdateNamespaceRequest)(nil), // 14: flipt.UpdateNamespaceRequest - (*DeleteNamespaceRequest)(nil), // 15: flipt.DeleteNamespaceRequest - (*Flag)(nil), // 16: flipt.Flag - (*FlagList)(nil), // 17: flipt.FlagList - (*GetFlagRequest)(nil), // 18: flipt.GetFlagRequest - (*ListFlagRequest)(nil), // 19: flipt.ListFlagRequest - (*CreateFlagRequest)(nil), // 20: flipt.CreateFlagRequest - (*UpdateFlagRequest)(nil), // 21: flipt.UpdateFlagRequest - (*DeleteFlagRequest)(nil), // 22: flipt.DeleteFlagRequest - (*Variant)(nil), // 23: flipt.Variant - (*CreateVariantRequest)(nil), // 24: flipt.CreateVariantRequest - (*UpdateVariantRequest)(nil), // 25: flipt.UpdateVariantRequest - (*DeleteVariantRequest)(nil), // 26: flipt.DeleteVariantRequest - (*Segment)(nil), // 27: flipt.Segment - (*SegmentList)(nil), // 28: flipt.SegmentList - (*GetSegmentRequest)(nil), // 29: flipt.GetSegmentRequest - (*ListSegmentRequest)(nil), // 30: flipt.ListSegmentRequest - (*CreateSegmentRequest)(nil), // 31: flipt.CreateSegmentRequest - (*UpdateSegmentRequest)(nil), // 32: flipt.UpdateSegmentRequest - (*DeleteSegmentRequest)(nil), // 33: flipt.DeleteSegmentRequest - (*Constraint)(nil), // 34: flipt.Constraint - (*CreateConstraintRequest)(nil), // 35: flipt.CreateConstraintRequest - (*UpdateConstraintRequest)(nil), // 36: flipt.UpdateConstraintRequest - (*DeleteConstraintRequest)(nil), // 37: flipt.DeleteConstraintRequest - (*Rollout)(nil), // 38: flipt.Rollout - (*RolloutSegment)(nil), // 39: flipt.RolloutSegment - (*RolloutThreshold)(nil), // 40: flipt.RolloutThreshold - (*RolloutList)(nil), // 41: flipt.RolloutList - (*ListRolloutRequest)(nil), // 42: flipt.ListRolloutRequest - (*GetRolloutRequest)(nil), // 43: flipt.GetRolloutRequest - (*CreateRolloutRequest)(nil), // 44: flipt.CreateRolloutRequest - (*UpdateRolloutRequest)(nil), // 45: flipt.UpdateRolloutRequest - (*DeleteRolloutRequest)(nil), // 46: flipt.DeleteRolloutRequest - (*OrderRolloutsRequest)(nil), // 47: flipt.OrderRolloutsRequest - (*Rule)(nil), // 48: flipt.Rule - (*RuleList)(nil), // 49: flipt.RuleList - (*ListRuleRequest)(nil), // 50: flipt.ListRuleRequest - (*GetRuleRequest)(nil), // 51: flipt.GetRuleRequest - (*CreateRuleRequest)(nil), // 52: flipt.CreateRuleRequest - (*UpdateRuleRequest)(nil), // 53: flipt.UpdateRuleRequest - (*DeleteRuleRequest)(nil), // 54: flipt.DeleteRuleRequest - (*OrderRulesRequest)(nil), // 55: flipt.OrderRulesRequest - (*Distribution)(nil), // 56: flipt.Distribution - (*CreateDistributionRequest)(nil), // 57: flipt.CreateDistributionRequest - (*UpdateDistributionRequest)(nil), // 58: flipt.UpdateDistributionRequest - (*DeleteDistributionRequest)(nil), // 59: flipt.DeleteDistributionRequest - nil, // 60: flipt.EvaluationRequest.ContextEntry - nil, // 61: flipt.EvaluationResponse.RequestContextEntry - (*timestamppb.Timestamp)(nil), // 62: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 63: google.protobuf.Empty + (SegmentOperator)(0), // 5: flipt.SegmentOperator + (*EvaluationRequest)(nil), // 6: flipt.EvaluationRequest + (*BatchEvaluationRequest)(nil), // 7: flipt.BatchEvaluationRequest + (*BatchEvaluationResponse)(nil), // 8: flipt.BatchEvaluationResponse + (*EvaluationResponse)(nil), // 9: flipt.EvaluationResponse + (*Namespace)(nil), // 10: flipt.Namespace + (*NamespaceList)(nil), // 11: flipt.NamespaceList + (*GetNamespaceRequest)(nil), // 12: flipt.GetNamespaceRequest + (*ListNamespaceRequest)(nil), // 13: flipt.ListNamespaceRequest + (*CreateNamespaceRequest)(nil), // 14: flipt.CreateNamespaceRequest + (*UpdateNamespaceRequest)(nil), // 15: flipt.UpdateNamespaceRequest + (*DeleteNamespaceRequest)(nil), // 16: flipt.DeleteNamespaceRequest + (*Flag)(nil), // 17: flipt.Flag + (*FlagList)(nil), // 18: flipt.FlagList + (*GetFlagRequest)(nil), // 19: flipt.GetFlagRequest + (*ListFlagRequest)(nil), // 20: flipt.ListFlagRequest + (*CreateFlagRequest)(nil), // 21: flipt.CreateFlagRequest + (*UpdateFlagRequest)(nil), // 22: flipt.UpdateFlagRequest + (*DeleteFlagRequest)(nil), // 23: flipt.DeleteFlagRequest + (*Variant)(nil), // 24: flipt.Variant + (*CreateVariantRequest)(nil), // 25: flipt.CreateVariantRequest + (*UpdateVariantRequest)(nil), // 26: flipt.UpdateVariantRequest + (*DeleteVariantRequest)(nil), // 27: flipt.DeleteVariantRequest + (*Segment)(nil), // 28: flipt.Segment + (*SegmentList)(nil), // 29: flipt.SegmentList + (*GetSegmentRequest)(nil), // 30: flipt.GetSegmentRequest + (*ListSegmentRequest)(nil), // 31: flipt.ListSegmentRequest + (*CreateSegmentRequest)(nil), // 32: flipt.CreateSegmentRequest + (*UpdateSegmentRequest)(nil), // 33: flipt.UpdateSegmentRequest + (*DeleteSegmentRequest)(nil), // 34: flipt.DeleteSegmentRequest + (*Constraint)(nil), // 35: flipt.Constraint + (*CreateConstraintRequest)(nil), // 36: flipt.CreateConstraintRequest + (*UpdateConstraintRequest)(nil), // 37: flipt.UpdateConstraintRequest + (*DeleteConstraintRequest)(nil), // 38: flipt.DeleteConstraintRequest + (*Rollout)(nil), // 39: flipt.Rollout + (*RolloutSegment)(nil), // 40: flipt.RolloutSegment + (*RolloutThreshold)(nil), // 41: flipt.RolloutThreshold + (*RolloutList)(nil), // 42: flipt.RolloutList + (*ListRolloutRequest)(nil), // 43: flipt.ListRolloutRequest + (*GetRolloutRequest)(nil), // 44: flipt.GetRolloutRequest + (*CreateRolloutRequest)(nil), // 45: flipt.CreateRolloutRequest + (*UpdateRolloutRequest)(nil), // 46: flipt.UpdateRolloutRequest + (*DeleteRolloutRequest)(nil), // 47: flipt.DeleteRolloutRequest + (*OrderRolloutsRequest)(nil), // 48: flipt.OrderRolloutsRequest + (*Rule)(nil), // 49: flipt.Rule + (*RuleList)(nil), // 50: flipt.RuleList + (*ListRuleRequest)(nil), // 51: flipt.ListRuleRequest + (*GetRuleRequest)(nil), // 52: flipt.GetRuleRequest + (*CreateRuleRequest)(nil), // 53: flipt.CreateRuleRequest + (*UpdateRuleRequest)(nil), // 54: flipt.UpdateRuleRequest + (*DeleteRuleRequest)(nil), // 55: flipt.DeleteRuleRequest + (*OrderRulesRequest)(nil), // 56: flipt.OrderRulesRequest + (*Distribution)(nil), // 57: flipt.Distribution + (*CreateDistributionRequest)(nil), // 58: flipt.CreateDistributionRequest + (*UpdateDistributionRequest)(nil), // 59: flipt.UpdateDistributionRequest + (*DeleteDistributionRequest)(nil), // 60: flipt.DeleteDistributionRequest + nil, // 61: flipt.EvaluationRequest.ContextEntry + nil, // 62: flipt.EvaluationResponse.RequestContextEntry + (*timestamppb.Timestamp)(nil), // 63: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 64: google.protobuf.Empty } var file_flipt_proto_depIdxs = []int32{ - 60, // 0: flipt.EvaluationRequest.context:type_name -> flipt.EvaluationRequest.ContextEntry - 5, // 1: flipt.BatchEvaluationRequest.requests:type_name -> flipt.EvaluationRequest - 8, // 2: flipt.BatchEvaluationResponse.responses:type_name -> flipt.EvaluationResponse - 61, // 3: flipt.EvaluationResponse.request_context:type_name -> flipt.EvaluationResponse.RequestContextEntry - 62, // 4: flipt.EvaluationResponse.timestamp:type_name -> google.protobuf.Timestamp + 61, // 0: flipt.EvaluationRequest.context:type_name -> flipt.EvaluationRequest.ContextEntry + 6, // 1: flipt.BatchEvaluationRequest.requests:type_name -> flipt.EvaluationRequest + 9, // 2: flipt.BatchEvaluationResponse.responses:type_name -> flipt.EvaluationResponse + 62, // 3: flipt.EvaluationResponse.request_context:type_name -> flipt.EvaluationResponse.RequestContextEntry + 63, // 4: flipt.EvaluationResponse.timestamp:type_name -> google.protobuf.Timestamp 0, // 5: flipt.EvaluationResponse.reason:type_name -> flipt.EvaluationReason - 62, // 6: flipt.Namespace.created_at:type_name -> google.protobuf.Timestamp - 62, // 7: flipt.Namespace.updated_at:type_name -> google.protobuf.Timestamp - 9, // 8: flipt.NamespaceList.namespaces:type_name -> flipt.Namespace - 62, // 9: flipt.Flag.created_at:type_name -> google.protobuf.Timestamp - 62, // 10: flipt.Flag.updated_at:type_name -> google.protobuf.Timestamp - 23, // 11: flipt.Flag.variants:type_name -> flipt.Variant + 63, // 6: flipt.Namespace.created_at:type_name -> google.protobuf.Timestamp + 63, // 7: flipt.Namespace.updated_at:type_name -> google.protobuf.Timestamp + 10, // 8: flipt.NamespaceList.namespaces:type_name -> flipt.Namespace + 63, // 9: flipt.Flag.created_at:type_name -> google.protobuf.Timestamp + 63, // 10: flipt.Flag.updated_at:type_name -> google.protobuf.Timestamp + 24, // 11: flipt.Flag.variants:type_name -> flipt.Variant 1, // 12: flipt.Flag.type:type_name -> flipt.FlagType - 16, // 13: flipt.FlagList.flags:type_name -> flipt.Flag + 17, // 13: flipt.FlagList.flags:type_name -> flipt.Flag 1, // 14: flipt.CreateFlagRequest.type:type_name -> flipt.FlagType - 62, // 15: flipt.Variant.created_at:type_name -> google.protobuf.Timestamp - 62, // 16: flipt.Variant.updated_at:type_name -> google.protobuf.Timestamp - 62, // 17: flipt.Segment.created_at:type_name -> google.protobuf.Timestamp - 62, // 18: flipt.Segment.updated_at:type_name -> google.protobuf.Timestamp - 34, // 19: flipt.Segment.constraints:type_name -> flipt.Constraint + 63, // 15: flipt.Variant.created_at:type_name -> google.protobuf.Timestamp + 63, // 16: flipt.Variant.updated_at:type_name -> google.protobuf.Timestamp + 63, // 17: flipt.Segment.created_at:type_name -> google.protobuf.Timestamp + 63, // 18: flipt.Segment.updated_at:type_name -> google.protobuf.Timestamp + 35, // 19: flipt.Segment.constraints:type_name -> flipt.Constraint 2, // 20: flipt.Segment.match_type:type_name -> flipt.MatchType - 27, // 21: flipt.SegmentList.segments:type_name -> flipt.Segment + 28, // 21: flipt.SegmentList.segments:type_name -> flipt.Segment 2, // 22: flipt.CreateSegmentRequest.match_type:type_name -> flipt.MatchType 2, // 23: flipt.UpdateSegmentRequest.match_type:type_name -> flipt.MatchType 3, // 24: flipt.Constraint.type:type_name -> flipt.ComparisonType - 62, // 25: flipt.Constraint.created_at:type_name -> google.protobuf.Timestamp - 62, // 26: flipt.Constraint.updated_at:type_name -> google.protobuf.Timestamp + 63, // 25: flipt.Constraint.created_at:type_name -> google.protobuf.Timestamp + 63, // 26: flipt.Constraint.updated_at:type_name -> google.protobuf.Timestamp 3, // 27: flipt.CreateConstraintRequest.type:type_name -> flipt.ComparisonType 3, // 28: flipt.UpdateConstraintRequest.type:type_name -> flipt.ComparisonType 4, // 29: flipt.Rollout.type:type_name -> flipt.RolloutType - 62, // 30: flipt.Rollout.created_at:type_name -> google.protobuf.Timestamp - 62, // 31: flipt.Rollout.updated_at:type_name -> google.protobuf.Timestamp - 39, // 32: flipt.Rollout.segment:type_name -> flipt.RolloutSegment - 40, // 33: flipt.Rollout.threshold:type_name -> flipt.RolloutThreshold - 38, // 34: flipt.RolloutList.rules:type_name -> flipt.Rollout - 39, // 35: flipt.CreateRolloutRequest.segment:type_name -> flipt.RolloutSegment - 40, // 36: flipt.CreateRolloutRequest.threshold:type_name -> flipt.RolloutThreshold - 39, // 37: flipt.UpdateRolloutRequest.segment:type_name -> flipt.RolloutSegment - 40, // 38: flipt.UpdateRolloutRequest.threshold:type_name -> flipt.RolloutThreshold - 56, // 39: flipt.Rule.distributions:type_name -> flipt.Distribution - 62, // 40: flipt.Rule.created_at:type_name -> google.protobuf.Timestamp - 62, // 41: flipt.Rule.updated_at:type_name -> google.protobuf.Timestamp - 48, // 42: flipt.RuleList.rules:type_name -> flipt.Rule - 62, // 43: flipt.Distribution.created_at:type_name -> google.protobuf.Timestamp - 62, // 44: flipt.Distribution.updated_at:type_name -> google.protobuf.Timestamp - 5, // 45: flipt.Flipt.Evaluate:input_type -> flipt.EvaluationRequest - 6, // 46: flipt.Flipt.BatchEvaluate:input_type -> flipt.BatchEvaluationRequest - 11, // 47: flipt.Flipt.GetNamespace:input_type -> flipt.GetNamespaceRequest - 12, // 48: flipt.Flipt.ListNamespaces:input_type -> flipt.ListNamespaceRequest - 13, // 49: flipt.Flipt.CreateNamespace:input_type -> flipt.CreateNamespaceRequest - 14, // 50: flipt.Flipt.UpdateNamespace:input_type -> flipt.UpdateNamespaceRequest - 15, // 51: flipt.Flipt.DeleteNamespace:input_type -> flipt.DeleteNamespaceRequest - 18, // 52: flipt.Flipt.GetFlag:input_type -> flipt.GetFlagRequest - 19, // 53: flipt.Flipt.ListFlags:input_type -> flipt.ListFlagRequest - 20, // 54: flipt.Flipt.CreateFlag:input_type -> flipt.CreateFlagRequest - 21, // 55: flipt.Flipt.UpdateFlag:input_type -> flipt.UpdateFlagRequest - 22, // 56: flipt.Flipt.DeleteFlag:input_type -> flipt.DeleteFlagRequest - 24, // 57: flipt.Flipt.CreateVariant:input_type -> flipt.CreateVariantRequest - 25, // 58: flipt.Flipt.UpdateVariant:input_type -> flipt.UpdateVariantRequest - 26, // 59: flipt.Flipt.DeleteVariant:input_type -> flipt.DeleteVariantRequest - 51, // 60: flipt.Flipt.GetRule:input_type -> flipt.GetRuleRequest - 50, // 61: flipt.Flipt.ListRules:input_type -> flipt.ListRuleRequest - 52, // 62: flipt.Flipt.CreateRule:input_type -> flipt.CreateRuleRequest - 53, // 63: flipt.Flipt.UpdateRule:input_type -> flipt.UpdateRuleRequest - 55, // 64: flipt.Flipt.OrderRules:input_type -> flipt.OrderRulesRequest - 54, // 65: flipt.Flipt.DeleteRule:input_type -> flipt.DeleteRuleRequest - 43, // 66: flipt.Flipt.GetRollout:input_type -> flipt.GetRolloutRequest - 42, // 67: flipt.Flipt.ListRollouts:input_type -> flipt.ListRolloutRequest - 44, // 68: flipt.Flipt.CreateRollout:input_type -> flipt.CreateRolloutRequest - 45, // 69: flipt.Flipt.UpdateRollout:input_type -> flipt.UpdateRolloutRequest - 46, // 70: flipt.Flipt.DeleteRollout:input_type -> flipt.DeleteRolloutRequest - 47, // 71: flipt.Flipt.OrderRollouts:input_type -> flipt.OrderRolloutsRequest - 57, // 72: flipt.Flipt.CreateDistribution:input_type -> flipt.CreateDistributionRequest - 58, // 73: flipt.Flipt.UpdateDistribution:input_type -> flipt.UpdateDistributionRequest - 59, // 74: flipt.Flipt.DeleteDistribution:input_type -> flipt.DeleteDistributionRequest - 29, // 75: flipt.Flipt.GetSegment:input_type -> flipt.GetSegmentRequest - 30, // 76: flipt.Flipt.ListSegments:input_type -> flipt.ListSegmentRequest - 31, // 77: flipt.Flipt.CreateSegment:input_type -> flipt.CreateSegmentRequest - 32, // 78: flipt.Flipt.UpdateSegment:input_type -> flipt.UpdateSegmentRequest - 33, // 79: flipt.Flipt.DeleteSegment:input_type -> flipt.DeleteSegmentRequest - 35, // 80: flipt.Flipt.CreateConstraint:input_type -> flipt.CreateConstraintRequest - 36, // 81: flipt.Flipt.UpdateConstraint:input_type -> flipt.UpdateConstraintRequest - 37, // 82: flipt.Flipt.DeleteConstraint:input_type -> flipt.DeleteConstraintRequest - 8, // 83: flipt.Flipt.Evaluate:output_type -> flipt.EvaluationResponse - 7, // 84: flipt.Flipt.BatchEvaluate:output_type -> flipt.BatchEvaluationResponse - 9, // 85: flipt.Flipt.GetNamespace:output_type -> flipt.Namespace - 10, // 86: flipt.Flipt.ListNamespaces:output_type -> flipt.NamespaceList - 9, // 87: flipt.Flipt.CreateNamespace:output_type -> flipt.Namespace - 9, // 88: flipt.Flipt.UpdateNamespace:output_type -> flipt.Namespace - 63, // 89: flipt.Flipt.DeleteNamespace:output_type -> google.protobuf.Empty - 16, // 90: flipt.Flipt.GetFlag:output_type -> flipt.Flag - 17, // 91: flipt.Flipt.ListFlags:output_type -> flipt.FlagList - 16, // 92: flipt.Flipt.CreateFlag:output_type -> flipt.Flag - 16, // 93: flipt.Flipt.UpdateFlag:output_type -> flipt.Flag - 63, // 94: flipt.Flipt.DeleteFlag:output_type -> google.protobuf.Empty - 23, // 95: flipt.Flipt.CreateVariant:output_type -> flipt.Variant - 23, // 96: flipt.Flipt.UpdateVariant:output_type -> flipt.Variant - 63, // 97: flipt.Flipt.DeleteVariant:output_type -> google.protobuf.Empty - 48, // 98: flipt.Flipt.GetRule:output_type -> flipt.Rule - 49, // 99: flipt.Flipt.ListRules:output_type -> flipt.RuleList - 48, // 100: flipt.Flipt.CreateRule:output_type -> flipt.Rule - 48, // 101: flipt.Flipt.UpdateRule:output_type -> flipt.Rule - 63, // 102: flipt.Flipt.OrderRules:output_type -> google.protobuf.Empty - 63, // 103: flipt.Flipt.DeleteRule:output_type -> google.protobuf.Empty - 38, // 104: flipt.Flipt.GetRollout:output_type -> flipt.Rollout - 41, // 105: flipt.Flipt.ListRollouts:output_type -> flipt.RolloutList - 38, // 106: flipt.Flipt.CreateRollout:output_type -> flipt.Rollout - 38, // 107: flipt.Flipt.UpdateRollout:output_type -> flipt.Rollout - 63, // 108: flipt.Flipt.DeleteRollout:output_type -> google.protobuf.Empty - 63, // 109: flipt.Flipt.OrderRollouts:output_type -> google.protobuf.Empty - 56, // 110: flipt.Flipt.CreateDistribution:output_type -> flipt.Distribution - 56, // 111: flipt.Flipt.UpdateDistribution:output_type -> flipt.Distribution - 63, // 112: flipt.Flipt.DeleteDistribution:output_type -> google.protobuf.Empty - 27, // 113: flipt.Flipt.GetSegment:output_type -> flipt.Segment - 28, // 114: flipt.Flipt.ListSegments:output_type -> flipt.SegmentList - 27, // 115: flipt.Flipt.CreateSegment:output_type -> flipt.Segment - 27, // 116: flipt.Flipt.UpdateSegment:output_type -> flipt.Segment - 63, // 117: flipt.Flipt.DeleteSegment:output_type -> google.protobuf.Empty - 34, // 118: flipt.Flipt.CreateConstraint:output_type -> flipt.Constraint - 34, // 119: flipt.Flipt.UpdateConstraint:output_type -> flipt.Constraint - 63, // 120: flipt.Flipt.DeleteConstraint:output_type -> google.protobuf.Empty - 83, // [83:121] is the sub-list for method output_type - 45, // [45:83] is the sub-list for method input_type - 45, // [45:45] is the sub-list for extension type_name - 45, // [45:45] is the sub-list for extension extendee - 0, // [0:45] is the sub-list for field type_name + 63, // 30: flipt.Rollout.created_at:type_name -> google.protobuf.Timestamp + 63, // 31: flipt.Rollout.updated_at:type_name -> google.protobuf.Timestamp + 40, // 32: flipt.Rollout.segment:type_name -> flipt.RolloutSegment + 41, // 33: flipt.Rollout.threshold:type_name -> flipt.RolloutThreshold + 5, // 34: flipt.RolloutSegment.segment_operator:type_name -> flipt.SegmentOperator + 39, // 35: flipt.RolloutList.rules:type_name -> flipt.Rollout + 40, // 36: flipt.CreateRolloutRequest.segment:type_name -> flipt.RolloutSegment + 41, // 37: flipt.CreateRolloutRequest.threshold:type_name -> flipt.RolloutThreshold + 40, // 38: flipt.UpdateRolloutRequest.segment:type_name -> flipt.RolloutSegment + 41, // 39: flipt.UpdateRolloutRequest.threshold:type_name -> flipt.RolloutThreshold + 57, // 40: flipt.Rule.distributions:type_name -> flipt.Distribution + 63, // 41: flipt.Rule.created_at:type_name -> google.protobuf.Timestamp + 63, // 42: flipt.Rule.updated_at:type_name -> google.protobuf.Timestamp + 5, // 43: flipt.Rule.segment_operator:type_name -> flipt.SegmentOperator + 49, // 44: flipt.RuleList.rules:type_name -> flipt.Rule + 5, // 45: flipt.CreateRuleRequest.segment_operator:type_name -> flipt.SegmentOperator + 5, // 46: flipt.UpdateRuleRequest.segment_operator:type_name -> flipt.SegmentOperator + 63, // 47: flipt.Distribution.created_at:type_name -> google.protobuf.Timestamp + 63, // 48: flipt.Distribution.updated_at:type_name -> google.protobuf.Timestamp + 6, // 49: flipt.Flipt.Evaluate:input_type -> flipt.EvaluationRequest + 7, // 50: flipt.Flipt.BatchEvaluate:input_type -> flipt.BatchEvaluationRequest + 12, // 51: flipt.Flipt.GetNamespace:input_type -> flipt.GetNamespaceRequest + 13, // 52: flipt.Flipt.ListNamespaces:input_type -> flipt.ListNamespaceRequest + 14, // 53: flipt.Flipt.CreateNamespace:input_type -> flipt.CreateNamespaceRequest + 15, // 54: flipt.Flipt.UpdateNamespace:input_type -> flipt.UpdateNamespaceRequest + 16, // 55: flipt.Flipt.DeleteNamespace:input_type -> flipt.DeleteNamespaceRequest + 19, // 56: flipt.Flipt.GetFlag:input_type -> flipt.GetFlagRequest + 20, // 57: flipt.Flipt.ListFlags:input_type -> flipt.ListFlagRequest + 21, // 58: flipt.Flipt.CreateFlag:input_type -> flipt.CreateFlagRequest + 22, // 59: flipt.Flipt.UpdateFlag:input_type -> flipt.UpdateFlagRequest + 23, // 60: flipt.Flipt.DeleteFlag:input_type -> flipt.DeleteFlagRequest + 25, // 61: flipt.Flipt.CreateVariant:input_type -> flipt.CreateVariantRequest + 26, // 62: flipt.Flipt.UpdateVariant:input_type -> flipt.UpdateVariantRequest + 27, // 63: flipt.Flipt.DeleteVariant:input_type -> flipt.DeleteVariantRequest + 52, // 64: flipt.Flipt.GetRule:input_type -> flipt.GetRuleRequest + 51, // 65: flipt.Flipt.ListRules:input_type -> flipt.ListRuleRequest + 53, // 66: flipt.Flipt.CreateRule:input_type -> flipt.CreateRuleRequest + 54, // 67: flipt.Flipt.UpdateRule:input_type -> flipt.UpdateRuleRequest + 56, // 68: flipt.Flipt.OrderRules:input_type -> flipt.OrderRulesRequest + 55, // 69: flipt.Flipt.DeleteRule:input_type -> flipt.DeleteRuleRequest + 44, // 70: flipt.Flipt.GetRollout:input_type -> flipt.GetRolloutRequest + 43, // 71: flipt.Flipt.ListRollouts:input_type -> flipt.ListRolloutRequest + 45, // 72: flipt.Flipt.CreateRollout:input_type -> flipt.CreateRolloutRequest + 46, // 73: flipt.Flipt.UpdateRollout:input_type -> flipt.UpdateRolloutRequest + 47, // 74: flipt.Flipt.DeleteRollout:input_type -> flipt.DeleteRolloutRequest + 48, // 75: flipt.Flipt.OrderRollouts:input_type -> flipt.OrderRolloutsRequest + 58, // 76: flipt.Flipt.CreateDistribution:input_type -> flipt.CreateDistributionRequest + 59, // 77: flipt.Flipt.UpdateDistribution:input_type -> flipt.UpdateDistributionRequest + 60, // 78: flipt.Flipt.DeleteDistribution:input_type -> flipt.DeleteDistributionRequest + 30, // 79: flipt.Flipt.GetSegment:input_type -> flipt.GetSegmentRequest + 31, // 80: flipt.Flipt.ListSegments:input_type -> flipt.ListSegmentRequest + 32, // 81: flipt.Flipt.CreateSegment:input_type -> flipt.CreateSegmentRequest + 33, // 82: flipt.Flipt.UpdateSegment:input_type -> flipt.UpdateSegmentRequest + 34, // 83: flipt.Flipt.DeleteSegment:input_type -> flipt.DeleteSegmentRequest + 36, // 84: flipt.Flipt.CreateConstraint:input_type -> flipt.CreateConstraintRequest + 37, // 85: flipt.Flipt.UpdateConstraint:input_type -> flipt.UpdateConstraintRequest + 38, // 86: flipt.Flipt.DeleteConstraint:input_type -> flipt.DeleteConstraintRequest + 9, // 87: flipt.Flipt.Evaluate:output_type -> flipt.EvaluationResponse + 8, // 88: flipt.Flipt.BatchEvaluate:output_type -> flipt.BatchEvaluationResponse + 10, // 89: flipt.Flipt.GetNamespace:output_type -> flipt.Namespace + 11, // 90: flipt.Flipt.ListNamespaces:output_type -> flipt.NamespaceList + 10, // 91: flipt.Flipt.CreateNamespace:output_type -> flipt.Namespace + 10, // 92: flipt.Flipt.UpdateNamespace:output_type -> flipt.Namespace + 64, // 93: flipt.Flipt.DeleteNamespace:output_type -> google.protobuf.Empty + 17, // 94: flipt.Flipt.GetFlag:output_type -> flipt.Flag + 18, // 95: flipt.Flipt.ListFlags:output_type -> flipt.FlagList + 17, // 96: flipt.Flipt.CreateFlag:output_type -> flipt.Flag + 17, // 97: flipt.Flipt.UpdateFlag:output_type -> flipt.Flag + 64, // 98: flipt.Flipt.DeleteFlag:output_type -> google.protobuf.Empty + 24, // 99: flipt.Flipt.CreateVariant:output_type -> flipt.Variant + 24, // 100: flipt.Flipt.UpdateVariant:output_type -> flipt.Variant + 64, // 101: flipt.Flipt.DeleteVariant:output_type -> google.protobuf.Empty + 49, // 102: flipt.Flipt.GetRule:output_type -> flipt.Rule + 50, // 103: flipt.Flipt.ListRules:output_type -> flipt.RuleList + 49, // 104: flipt.Flipt.CreateRule:output_type -> flipt.Rule + 49, // 105: flipt.Flipt.UpdateRule:output_type -> flipt.Rule + 64, // 106: flipt.Flipt.OrderRules:output_type -> google.protobuf.Empty + 64, // 107: flipt.Flipt.DeleteRule:output_type -> google.protobuf.Empty + 39, // 108: flipt.Flipt.GetRollout:output_type -> flipt.Rollout + 42, // 109: flipt.Flipt.ListRollouts:output_type -> flipt.RolloutList + 39, // 110: flipt.Flipt.CreateRollout:output_type -> flipt.Rollout + 39, // 111: flipt.Flipt.UpdateRollout:output_type -> flipt.Rollout + 64, // 112: flipt.Flipt.DeleteRollout:output_type -> google.protobuf.Empty + 64, // 113: flipt.Flipt.OrderRollouts:output_type -> google.protobuf.Empty + 57, // 114: flipt.Flipt.CreateDistribution:output_type -> flipt.Distribution + 57, // 115: flipt.Flipt.UpdateDistribution:output_type -> flipt.Distribution + 64, // 116: flipt.Flipt.DeleteDistribution:output_type -> google.protobuf.Empty + 28, // 117: flipt.Flipt.GetSegment:output_type -> flipt.Segment + 29, // 118: flipt.Flipt.ListSegments:output_type -> flipt.SegmentList + 28, // 119: flipt.Flipt.CreateSegment:output_type -> flipt.Segment + 28, // 120: flipt.Flipt.UpdateSegment:output_type -> flipt.Segment + 64, // 121: flipt.Flipt.DeleteSegment:output_type -> google.protobuf.Empty + 35, // 122: flipt.Flipt.CreateConstraint:output_type -> flipt.Constraint + 35, // 123: flipt.Flipt.UpdateConstraint:output_type -> flipt.Constraint + 64, // 124: flipt.Flipt.DeleteConstraint:output_type -> google.protobuf.Empty + 87, // [87:125] is the sub-list for method output_type + 49, // [49:87] is the sub-list for method input_type + 49, // [49:49] is the sub-list for extension type_name + 49, // [49:49] is the sub-list for extension extendee + 0, // [0:49] is the sub-list for field type_name } func init() { file_flipt_proto_init() } @@ -6238,7 +6402,7 @@ func file_flipt_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_flipt_proto_rawDesc, - NumEnums: 5, + NumEnums: 6, NumMessages: 57, NumExtensions: 0, NumServices: 1, diff --git a/rpc/flipt/flipt.proto b/rpc/flipt/flipt.proto index e78bafc2f0..bcbc0bef54 100644 --- a/rpc/flipt/flipt.proto +++ b/rpc/flipt/flipt.proto @@ -34,13 +34,14 @@ message EvaluationResponse { map request_context = 3; bool match = 4; string flag_key = 5; - string segment_key = 6; + string segment_key = 6 [deprecated = true]; google.protobuf.Timestamp timestamp = 7; string value = 8; double request_duration_millis = 9; string attachment = 10; EvaluationReason reason = 11; string namespace_key = 12; + repeated string segment_keys = 13; } enum EvaluationReason { @@ -295,6 +296,11 @@ enum RolloutType { THRESHOLD_ROLLOUT_TYPE = 2; } +enum SegmentOperator { + OR_SEGMENT_OPERATOR = 0; + AND_SEGMENT_OPERATOR = 1; +} + message Rollout { string id = 1; string namespace_key = 2; @@ -311,8 +317,10 @@ message Rollout { } message RolloutSegment { - string segment_key = 1; + string segment_key = 1 [deprecated = true]; bool value = 2; + repeated string segment_keys = 3; + SegmentOperator segment_operator = 4; } message RolloutThreshold { @@ -382,6 +390,8 @@ message Rule { google.protobuf.Timestamp created_at = 6; google.protobuf.Timestamp updated_at = 7; string namespace_key = 8; + repeated string segment_keys = 9; + SegmentOperator segment_operator = 10; } message RuleList { @@ -405,16 +415,20 @@ message GetRuleRequest { message CreateRuleRequest { string flag_key = 1; - string segment_key = 2; + string segment_key = 2 [deprecated = true]; int32 rank = 3; string namespace_key = 4; + repeated string segment_keys = 5; + SegmentOperator segment_operator = 6; } message UpdateRuleRequest { string id = 1; string flag_key = 2; - string segment_key = 3; + string segment_key = 3 [deprecated = true]; string namespace_key = 4; + repeated string segment_keys = 5; + SegmentOperator segment_operator = 6; } message DeleteRuleRequest { diff --git a/rpc/flipt/validation.go b/rpc/flipt/validation.go index f7b40ffce3..b336425dc5 100644 --- a/rpc/flipt/validation.go +++ b/rpc/flipt/validation.go @@ -184,8 +184,12 @@ func (req *CreateRuleRequest) Validate() error { return errors.EmptyFieldError("flagKey") } - if req.SegmentKey == "" { - return errors.EmptyFieldError("segmentKey") + if req.SegmentKey == "" && len(req.SegmentKeys) == 0 { + return errors.EmptyFieldError("segmentKey or segmentKeys") + } + + if req.SegmentKey != "" && len(req.SegmentKeys) > 0 { + return errors.InvalidFieldError("segmentKey or segmentKeys", "only one can be present") } if req.Rank <= 0 { @@ -204,8 +208,12 @@ func (req *UpdateRuleRequest) Validate() error { return errors.EmptyFieldError("flagKey") } - if req.SegmentKey == "" { - return errors.EmptyFieldError("segmentKey") + if req.SegmentKey == "" && len(req.SegmentKeys) == 0 { + return errors.EmptyFieldError("segmentKey or segmentKeys") + } + + if req.SegmentKey != "" && len(req.SegmentKeys) > 0 { + return errors.InvalidFieldError("segmentKey or segmentKeys", "only one can be present") } return nil @@ -529,8 +537,12 @@ func (req *CreateRolloutRequest) Validate() error { return errors.InvalidFieldError("threshold.percentage", "must be within range [0, 100]") } case *CreateRolloutRequest_Segment: - if rule.Segment.SegmentKey == "" { - return errors.EmptyFieldError("segmentKey") + if rule.Segment.SegmentKey == "" && len(rule.Segment.SegmentKeys) == 0 { + return errors.EmptyFieldError("segmentKey or segmentKeys") + } + + if rule.Segment.SegmentKey != "" && len(rule.Segment.SegmentKeys) > 0 { + return errors.InvalidFieldError("segmentKey or segmentKeys", "only one can be present") } } @@ -552,9 +564,14 @@ func (req *UpdateRolloutRequest) Validate() error { return errors.InvalidFieldError("threshold.percentage", "must be within range [0, 100]") } case *UpdateRolloutRequest_Segment: - if rule.Segment.SegmentKey == "" { - return errors.EmptyFieldError("segmentKey") + if rule.Segment.SegmentKey == "" && len(rule.Segment.SegmentKeys) == 0 { + return errors.EmptyFieldError("segmentKey or segmentKeys") } + + if rule.Segment.SegmentKey != "" && len(rule.Segment.SegmentKeys) > 0 { + return errors.InvalidFieldError("segmentKey or segmentKeys", "only one can be present") + } + } return nil diff --git a/ui/package-lock.json b/ui/package-lock.json index 9903d03dcf..407f9338ff 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -34,6 +34,7 @@ "react-redux": "^8.1.1", "react-router-dom": "^6.14.1", "swr": "^2.2.0", + "tailwind-merge": "^1.14.0", "uuid": "^9.0.0", "yup": "^0.32.11" }, @@ -11835,6 +11836,15 @@ "react": "^16.11.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/tailwind-merge": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", + "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", @@ -21058,6 +21068,11 @@ "use-sync-external-store": "^1.2.0" } }, + "tailwind-merge": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", + "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==" + }, "tailwindcss": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", diff --git a/ui/package.json b/ui/package.json index 6696dd0d0e..4fc1309f6b 100644 --- a/ui/package.json +++ b/ui/package.json @@ -38,6 +38,7 @@ "react-redux": "^8.1.1", "react-router-dom": "^6.14.1", "swr": "^2.2.0", + "tailwind-merge": "^1.14.0", "uuid": "^9.0.0", "yup": "^0.32.11" }, diff --git a/ui/src/app/flags/Evaluation.tsx b/ui/src/app/flags/Evaluation.tsx index 2696420544..bdd7bae147 100644 --- a/ui/src/app/flags/Evaluation.tsx +++ b/ui/src/app/flags/Evaluation.tsx @@ -34,7 +34,7 @@ import { IDistribution } from '~/types/Distribution'; import { IEvaluatable } from '~/types/Evaluatable'; import { FlagType } from '~/types/Flag'; import { IRule, IRuleList } from '~/types/Rule'; -import { ISegment, ISegmentList } from '~/types/Segment'; +import { ISegment, ISegmentList, SegmentOperatorType } from '~/types/Segment'; import { IVariant } from '~/types/Variant'; import { FlagProps } from './FlagProps'; @@ -87,17 +87,44 @@ export default function Evaluation() { } ); + const ruleSegments: ISegment[] = []; + + const size = rule.segmentKeys ? rule.segmentKeys.length : 0; + + // Combine both segment and segments for legacy purposes. + // TODO(yquansah): Should be removed once there are no more references to `segmentKey`. + for (let i = 0; i < size; i++) { + const ruleSegment = rule.segmentKeys && rule.segmentKeys[i]; + const segment = segments.find( + (segment: ISegment) => ruleSegment === segment.key + ); + if (segment) { + ruleSegments.push(segment); + } + } + const segment = segments.find( (segment: ISegment) => segment.key === rule.segmentKey ); - if (!segment) { + + if (segment) { + ruleSegments.push(segment); + } + + // If there are no ruleSegments return an empty array. + if (ruleSegments.length === 0) { return []; } + const operator = rule.segmentOperator + ? rule.segmentOperator + : SegmentOperatorType.OR; + return { id: rule.id, flag, - segment, + segments: ruleSegments, + operator, rank: rule.rank, rollouts, createdAt: rule.createdAt, diff --git a/ui/src/app/flags/rollouts/Rollouts.tsx b/ui/src/app/flags/rollouts/Rollouts.tsx index 7bea7f0b74..22a3e5a550 100644 --- a/ui/src/app/flags/rollouts/Rollouts.tsx +++ b/ui/src/app/flags/rollouts/Rollouts.tsx @@ -40,7 +40,7 @@ import { useError } from '~/data/hooks/error'; import { useSuccess } from '~/data/hooks/success'; import { IFlag } from '~/types/Flag'; import { IRollout, IRolloutList } from '~/types/Rollout'; -import { ISegment, ISegmentList } from '~/types/Segment'; +import { ISegment, ISegmentList, SegmentOperatorType } from '~/types/Segment'; type RolloutsProps = { flag: IFlag; @@ -84,7 +84,37 @@ export default function Rollouts(props: RolloutsProps) { flag.key )) as IRolloutList; - setRollouts(rolloutList.rules); + // Combine both segmentKey and segmentKeys for legacy purposes. + // TODO(yquansah): Should be removed once there are no more references to `segmentKey`. + const rolloutRules = rolloutList.rules.map((rollout) => { + if (rollout.segment) { + let segmentKeys: string[] = []; + if ( + rollout.segment.segmentKeys && + rollout.segment.segmentKeys.length > 0 + ) { + segmentKeys = rollout.segment.segmentKeys; + } else if (rollout.segment.segmentKey) { + segmentKeys = [rollout.segment.segmentKey]; + } + + return { + ...rollout, + segment: { + segmentOperator: + rollout.segment.segmentOperator || SegmentOperatorType.OR, + segmentKeys, + value: rollout.segment.value + } + }; + } + + return { + ...rollout + }; + }); + + setRollouts(rolloutRules); }, [namespace.key, flag.key]); const incrementRolloutsVersion = () => { diff --git a/ui/src/components/forms/Combobox.tsx b/ui/src/components/forms/Combobox.tsx index 6685896998..09f6b0fa43 100644 --- a/ui/src/components/forms/Combobox.tsx +++ b/ui/src/components/forms/Combobox.tsx @@ -2,6 +2,7 @@ import { Combobox as C } from '@headlessui/react'; import { CheckIcon, ChevronUpDownIcon } from '@heroicons/react/24/outline'; import { useField } from 'formik'; import { useState } from 'react'; +import { twMerge } from 'tailwind-merge'; import { IFilterable } from '~/types/Selectable'; import { classNames } from '~/utils/helpers'; @@ -14,6 +15,7 @@ type ComboboxProps = { setSelected?: (v: T | null) => void; disabled?: boolean; className?: string; + inputClassName?: string; }; export default function Combobox( @@ -22,6 +24,7 @@ export default function Combobox( const { id, className, + inputClassName, values, selected, setSelected, @@ -51,7 +54,9 @@ export default function Combobox(
) => { setQuery(e.target.value); }} diff --git a/ui/src/components/forms/SegmentsPicker.tsx b/ui/src/components/forms/SegmentsPicker.tsx new file mode 100644 index 0000000000..8c79354d1d --- /dev/null +++ b/ui/src/components/forms/SegmentsPicker.tsx @@ -0,0 +1,154 @@ +import { MinusSmallIcon, PlusSmallIcon } from '@heroicons/react/24/outline'; +import { useEffect, useRef, useState } from 'react'; +import { twMerge } from 'tailwind-merge'; +import Combobox from '~/components/forms/Combobox'; +import { FilterableSegment, ISegment } from '~/types/Segment'; +import { truncateKey } from '~/utils/helpers'; + +type SegmentPickerProps = { + readonly?: boolean; + segments: ISegment[]; + selectedSegments: FilterableSegment[]; + segmentAdd: (segment: FilterableSegment) => void; + segmentReplace: (index: number, segment: FilterableSegment) => void; + segmentRemove: (index: number) => void; +}; + +export default function SegmentsPicker({ + readonly = false, + segments, + selectedSegments: parentSegments, + segmentAdd, + segmentReplace, + segmentRemove +}: SegmentPickerProps) { + const segmentsSet = useRef>( + new Set(parentSegments.map((s) => s.key)) + ); + + const [editing, setEditing] = useState(true); + + useEffect(() => { + setEditing(true); + }, [parentSegments]); + + const handleSegmentRemove = (index: number) => { + const filterableSegment = parentSegments[index]; + + // Remove references to the segment that is being deleted. + segmentsSet.current!.delete(filterableSegment.key); + segmentRemove(index); + + if (editing && parentSegments.length == 1) { + setEditing(true); + } + }; + + const handleSegmentSelected = ( + index: number, + segment: FilterableSegment | null + ) => { + if (!segment) { + return; + } + + const selectedSegmentList = [...parentSegments]; + const segmentSetCurrent = segmentsSet.current!; + + if (index <= parentSegments.length - 1) { + const previousSegment = selectedSegmentList[index]; + if (segmentSetCurrent.has(previousSegment.key)) { + segmentSetCurrent.delete(previousSegment.key); + } + + segmentSetCurrent.add(segment.key); + segmentReplace(index, segment); + } else { + segmentSetCurrent.add(segment.key); + segmentAdd(segment); + } + }; + + return ( +
+ {parentSegments.map((selectedSegment, index) => ( +
+
+ + id={`segmentKey-${index}`} + name={`segmentKey-${index}`} + placeholder="Select or search for a segment" + values={segments + .filter((s) => !segmentsSet.current!.has(s.key)) + .map((s) => ({ + ...s, + filterValue: truncateKey(s.key), + displayValue: s.name + }))} + disabled={readonly} + selected={selectedSegment} + setSelected={(filterableSegment) => { + handleSegmentSelected(index, filterableSegment); + }} + inputClassName={ + readonly + ? 'cursor-not-allowed bg-gray-100 text-gray-500' + : undefined + } + /> +
+ {editing && parentSegments.length - 1 === index ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+ ))} + {(!editing || parentSegments.length === 0) && ( +
+
+ + id={`segmentKey-${parentSegments.length}`} + name={`segmentKey-${parentSegments.length}`} + placeholder="Select or search for a segment" + values={segments + .filter((s) => !segmentsSet.current!.has(s.key)) + .map((s) => ({ + ...s, + filterValue: truncateKey(s.key), + displayValue: s.name + }))} + selected={null} + setSelected={(filterableSegment) => { + handleSegmentSelected(parentSegments.length, filterableSegment); + }} + /> +
+
+ )} +
+ ); +} diff --git a/ui/src/components/forms/Select.tsx b/ui/src/components/forms/Select.tsx index e8720e4288..5447060fda 100644 --- a/ui/src/components/forms/Select.tsx +++ b/ui/src/components/forms/Select.tsx @@ -1,4 +1,5 @@ import { useField } from 'formik'; +import { twMerge } from 'tailwind-merge'; type SelectProps = { id: string; @@ -8,10 +9,20 @@ type SelectProps = { className?: string; value?: string; onChange?: (e: React.ChangeEvent) => void; + disabled?: boolean; }; export default function Select(props: SelectProps) { - const { id, name, options, children, className, value, onChange } = props; + const { + id, + name, + options, + children, + className, + value, + onChange, + disabled = false + } = props; const [field] = useField({ name, @@ -23,9 +34,12 @@ export default function Select(props: SelectProps) { {...field} id={id} name={name} - className={`text-gray-900 bg-gray-50 border-gray-300 block rounded-md py-2 pl-3 pr-10 text-base focus:border-violet-300 focus:outline-none focus:ring-violet-300 sm:text-sm ${className}`} + className={twMerge( + `text-gray-900 bg-gray-50 border-gray-300 block rounded-md py-2 pl-3 pr-10 text-base focus:border-violet-300 focus:outline-none focus:ring-violet-300 sm:text-sm ${className}` + )} value={value} onChange={onChange || field.onChange} + disabled={disabled} > {options && options.map((option) => ( diff --git a/ui/src/components/rollouts/forms/EditRolloutForm.tsx b/ui/src/components/rollouts/forms/EditRolloutForm.tsx index fe9cc5342c..904eabeee5 100644 --- a/ui/src/components/rollouts/forms/EditRolloutForm.tsx +++ b/ui/src/components/rollouts/forms/EditRolloutForm.tsx @@ -1,12 +1,13 @@ import { Dialog } from '@headlessui/react'; import { XMarkIcon } from '@heroicons/react/24/outline'; -import { Form, Formik } from 'formik'; -import { useState } from 'react'; +import { FieldArray, Form, Formik } from 'formik'; import { useSelector } from 'react-redux'; +import { twMerge } from 'tailwind-merge'; +import { selectReadonly } from '~/app/meta/metaSlice'; import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice'; import Button from '~/components/forms/buttons/Button'; -import Combobox from '~/components/forms/Combobox'; import Input from '~/components/forms/Input'; +import SegmentsPicker from '~/components/forms/SegmentsPicker'; import Select from '~/components/forms/Select'; import Loading from '~/components/Loading'; import MoreInfo from '~/components/MoreInfo'; @@ -14,8 +15,12 @@ import { updateRollout } from '~/data/api'; import { useError } from '~/data/hooks/error'; import { useSuccess } from '~/data/hooks/success'; import { IRollout, RolloutType } from '~/types/Rollout'; -import { FilterableSegment, ISegment } from '~/types/Segment'; -import { truncateKey } from '~/utils/helpers'; +import { + FilterableSegment, + ISegment, + segmentOperators, + SegmentOperatorType +} from '~/types/Segment'; const rolloutRuleTypes = [ { @@ -39,9 +44,10 @@ type EditRolloutFormProps = { }; interface RolloutFormValues { - description?: string; - segmentKey?: string; + description: string; + operator?: SegmentOperatorType; percentage?: number; + segmentKeys?: FilterableSegment[]; value: string; } @@ -53,19 +59,12 @@ export default function EditRolloutForm(props: EditRolloutFormProps) { const namespace = useSelector(selectCurrentNamespace); - const [selectedSegment, setSelectedSegment] = - useState(() => { - let selected = - segments.find((s) => s.key === rollout.segment?.segmentKey) || null; - if (selected) { - return { - ...selected, - displayValue: selected.name, - filterValue: selected.key - }; - } - return null; - }); + const segmentOperator = + rollout.segment && rollout.segment.segmentOperator + ? rollout.segment.segmentOperator + : SegmentOperatorType.OR; + + const readOnly = useSelector(selectReadonly); const handleSegmentSubmit = (values: RolloutFormValues) => { let rolloutSegment = rollout; @@ -75,7 +74,8 @@ export default function EditRolloutForm(props: EditRolloutFormProps) { ...rolloutSegment, description: values.description, segment: { - segmentKey: values.segmentKey || '', + segmentKeys: values.segmentKeys?.map((s) => s.key), + segmentOperator: values.operator, value: values.value === 'true' } }); @@ -110,15 +110,24 @@ export default function EditRolloutForm(props: EditRolloutFormProps) { initialValues={{ type: rollout.type, description: rollout.description || '', - segmentKey: rollout.segment?.segmentKey || '', + segmentKeys: segments.flatMap((s) => + rollout.segment?.segmentKeys?.includes(s.key) + ? { + ...s, + displayValue: s.name, + filterValue: s.key + } + : [] + ), + operator: segmentOperator, percentage: rollout.threshold?.percentage, value: initialValue }} validate={(values) => { if (values.type === RolloutType.SEGMENT) { - if (!values.segmentKey) { + if (values.segmentKeys.length <= 0) { return { - segmentKey: true + segmentKeys: true }; } } else if (values.type === RolloutType.THRESHOLD) { @@ -265,25 +274,81 @@ export default function EditRolloutForm(props: EditRolloutFormProps) {
- - id="segmentKey" - name="segmentKey" - placeholder="Select or search for a segment" - values={segments.map((s) => ({ - ...s, - filterValue: truncateKey(s.key), - displayValue: s.name - }))} - selected={selectedSegment} - setSelected={setSelectedSegment} - /> +
+ ( + + arrayHelpers.push(segment) + } + segmentRemove={(index: number) => + arrayHelpers.remove(index) + } + segmentReplace={( + index: number, + segment: FilterableSegment + ) => arrayHelpers.replace(index, segment)} + selectedSegments={formik.values.segmentKeys} + /> + )} + /> +
+
+ {formik.values.segmentKeys.length > 1 && + segmentOperators.map((segmentOperator, index) => ( +
+
+ { + formik.setFieldValue( + 'operator', + segmentOperator.id + ); + }} + checked={ + segmentOperator.id === formik.values.operator + } + value={segmentOperator.id} + disabled={readOnly} + title={ + readOnly + ? 'Not allowed in Read-Only mode' + : undefined + } + /> +
+
+ +
+
+ ))} +
)} diff --git a/ui/src/components/rollouts/forms/QuickEditRolloutForm.tsx b/ui/src/components/rollouts/forms/QuickEditRolloutForm.tsx index 7b0834e042..b077c625ea 100644 --- a/ui/src/components/rollouts/forms/QuickEditRolloutForm.tsx +++ b/ui/src/components/rollouts/forms/QuickEditRolloutForm.tsx @@ -1,10 +1,11 @@ -import { Form, Formik } from 'formik'; -import { useState } from 'react'; +import { FieldArray, Form, Formik } from 'formik'; import { useSelector } from 'react-redux'; +import { twMerge } from 'tailwind-merge'; +import { selectReadonly } from '~/app/meta/metaSlice'; import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice'; import TextButton from '~/components/forms/buttons/TextButton'; -import Combobox from '~/components/forms/Combobox'; import Input from '~/components/forms/Input'; +import SegmentsPicker from '~/components/forms/SegmentsPicker'; import Select from '~/components/forms/Select'; import Loading from '~/components/Loading'; import { updateRollout } from '~/data/api'; @@ -12,8 +13,12 @@ import { useError } from '~/data/hooks/error'; import { useSuccess } from '~/data/hooks/success'; import { IFlag } from '~/types/Flag'; import { IRollout, RolloutType } from '~/types/Rollout'; -import { FilterableSegment, ISegment } from '~/types/Segment'; -import { truncateKey } from '~/utils/helpers'; +import { + FilterableSegment, + ISegment, + segmentOperators, + SegmentOperatorType +} from '~/types/Segment'; type QuickEditRolloutFormProps = { flag: IFlag; @@ -23,8 +28,9 @@ type QuickEditRolloutFormProps = { }; interface RolloutFormValues { - segmentKey?: string; + operator?: SegmentOperatorType; percentage?: number; + segmentKeys?: FilterableSegment[]; value: string; } @@ -36,19 +42,12 @@ export default function QuickEditRolloutForm(props: QuickEditRolloutFormProps) { const namespace = useSelector(selectCurrentNamespace); - const [selectedSegment, setSelectedSegment] = - useState(() => { - let selected = - segments.find((s) => s.key === rollout.segment?.segmentKey) || null; - if (selected) { - return { - ...selected, - displayValue: selected.name, - filterValue: selected.key - }; - } - return null; - }); + const segmentOperator = + rollout.segment && rollout.segment.segmentOperator + ? rollout.segment.segmentOperator + : SegmentOperatorType.OR; + + const readOnly = useSelector(selectReadonly); const handleSegmentSubmit = (values: RolloutFormValues) => { let rolloutSegment = rollout; @@ -57,7 +56,8 @@ export default function QuickEditRolloutForm(props: QuickEditRolloutFormProps) { return updateRollout(namespace.key, flag.key, rollout.id, { ...rolloutSegment, segment: { - segmentKey: values.segmentKey || '', + segmentKeys: values.segmentKeys?.map((s) => s.key), + segmentOperator: values.operator, value: values.value === 'true' } }); @@ -90,15 +90,24 @@ export default function QuickEditRolloutForm(props: QuickEditRolloutFormProps) { enableReinitialize initialValues={{ type: rollout.type, - segmentKey: rollout.segment?.segmentKey, + segmentKeys: segments.flatMap((s) => + rollout.segment?.segmentKeys?.includes(s.key) + ? { + ...s, + displayValue: s.name, + filterValue: s.key + } + : [] + ), percentage: rollout.threshold?.percentage, - value: initialValue + value: initialValue, + operator: segmentOperator }} validate={(values) => { if (values.type === RolloutType.SEGMENT) { - if (!values.segmentKey) { + if (values.segmentKeys.length <= 0) { return { - segmentKey: true + segmentKeys: true }; } } else if (values.type === RolloutType.THRESHOLD) { @@ -171,25 +180,81 @@ export default function QuickEditRolloutForm(props: QuickEditRolloutFormProps) {
- - id="segmentKey" - name="segmentKey" - placeholder="Select or search for a segment" - values={segments.map((s) => ({ - ...s, - filterValue: truncateKey(s.key), - displayValue: s.name - }))} - selected={selectedSegment} - setSelected={setSelectedSegment} - /> +
+ ( + + arrayHelpers.push(segment) + } + segmentRemove={(index: number) => + arrayHelpers.remove(index) + } + segmentReplace={( + index: number, + segment: FilterableSegment + ) => arrayHelpers.replace(index, segment)} + selectedSegments={formik.values.segmentKeys} + /> + )} + /> +
+
+ {formik.values.segmentKeys.length > 1 && + segmentOperators.map((segmentOperator, index) => ( +
+
+ { + formik.setFieldValue( + 'operator', + segmentOperator.id + ); + }} + checked={ + segmentOperator.id === formik.values.operator + } + value={segmentOperator.id} + disabled={readOnly} + title={ + readOnly + ? 'Not allowed in Read-Only mode' + : undefined + } + /> +
+
+ +
+
+ ))} +
)} @@ -208,7 +273,14 @@ export default function QuickEditRolloutForm(props: QuickEditRolloutFormProps) { { label: 'True', value: 'true' }, { label: 'False', value: 'false' } ]} - className="w-full cursor-pointer appearance-none self-center rounded-lg py-1 align-middle" + className={twMerge( + `w-full cursor-pointer appearance-none self-center rounded-lg py-1 align-middle ${ + readOnly + ? 'text-gray-500 bg-gray-100 cursor-not-allowed' + : undefined + }` + )} + disabled={readOnly} />
diff --git a/ui/src/components/rollouts/forms/RolloutForm.tsx b/ui/src/components/rollouts/forms/RolloutForm.tsx index ff5b4f9884..0e1f808983 100644 --- a/ui/src/components/rollouts/forms/RolloutForm.tsx +++ b/ui/src/components/rollouts/forms/RolloutForm.tsx @@ -1,12 +1,12 @@ import { Dialog } from '@headlessui/react'; import { XMarkIcon } from '@heroicons/react/24/outline'; -import { Form, Formik } from 'formik'; +import { FieldArray, Form, Formik } from 'formik'; import { useState } from 'react'; import { useSelector } from 'react-redux'; import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice'; import Button from '~/components/forms/buttons/Button'; -import Combobox from '~/components/forms/Combobox'; import Input from '~/components/forms/Input'; +import SegmentsPicker from '~/components/forms/SegmentsPicker'; import Select from '~/components/forms/Select'; import Loading from '~/components/Loading'; import MoreInfo from '~/components/MoreInfo'; @@ -14,8 +14,12 @@ import { createRollout } from '~/data/api'; import { useError } from '~/data/hooks/error'; import { useSuccess } from '~/data/hooks/success'; import { RolloutType } from '~/types/Rollout'; -import { FilterableSegment, ISegment } from '~/types/Segment'; -import { truncateKey } from '~/utils/helpers'; +import { + FilterableSegment, + ISegment, + segmentOperators, + SegmentOperatorType +} from '~/types/Segment'; const rolloutRuleTypes = [ { @@ -41,7 +45,8 @@ type RolloutFormProps = { interface RolloutFormValues { type: string; description?: string; - segmentKey?: string; + segmentKeys?: FilterableSegment[]; + operator?: SegmentOperatorType; percentage?: number; value: string; } @@ -55,8 +60,6 @@ export default function RolloutForm(props: RolloutFormProps) { const namespace = useSelector(selectCurrentNamespace); const [rolloutRuleType, setRolloutRuleType] = useState(RolloutType.THRESHOLD); - const [selectedSegment, setSelectedSegment] = - useState(null); const handleSegmentSubmit = (values: RolloutFormValues) => { return createRollout(namespace.key, flagKey, { @@ -64,7 +67,8 @@ export default function RolloutForm(props: RolloutFormProps) { type: rolloutRuleType, description: values.description, segment: { - segmentKey: values.segmentKey || '', + segmentKeys: values.segmentKeys?.map((s) => s.key), + segmentOperator: values.operator, value: values.value === 'true' } }); @@ -88,15 +92,16 @@ export default function RolloutForm(props: RolloutFormProps) { initialValues={{ type: rolloutRuleType, description: '', - segmentKey: '', + segmentKeys: [], + operator: SegmentOperatorType.OR, percentage: 50, // TODO: make this 0? value: 'true' }} validate={(values) => { if (values.type === RolloutType.SEGMENT) { - if (!values.segmentKey) { + if (values.segmentKeys.length <= 0) { return { - segmentKey: true + segmentKeys: true }; } } else if (values.type === RolloutType.THRESHOLD) { @@ -183,6 +188,7 @@ export default function RolloutForm(props: RolloutFormProps) { type="radio" className="text-violet-400 border-gray-300 h-4 w-4 focus:ring-violet-400" onChange={() => { + formik.setFieldValue('type', rolloutRule.id); setRolloutRuleType(rolloutRule.id); formik.setFieldValue('type', rolloutRule.id); }} @@ -250,19 +256,78 @@ export default function RolloutForm(props: RolloutFormProps) {
- - id="segmentKey" - name="segmentKey" - placeholder="Select or search for a segment" - values={segments.map((s) => ({ - ...s, - filterValue: truncateKey(s.key), - displayValue: s.name - }))} - selected={selectedSegment} - setSelected={setSelectedSegment} + ( + + arrayHelpers.push(segment) + } + segmentRemove={(index: number) => + arrayHelpers.remove(index) + } + segmentReplace={( + index: number, + segment: FilterableSegment + ) => arrayHelpers.replace(index, segment)} + selectedSegments={formik.values.segmentKeys} + /> + )} />
+ {formik.values.segmentKeys.length > 1 && ( + <> +
+ +
+
+
+
+ {segmentOperators.map((segmentOperator, index) => ( +
+
+ { + formik.setFieldValue( + 'operator', + segmentOperator.id + ); + }} + checked={ + segmentOperator.id === + formik.values.operator + } + value={segmentOperator.id} + /> +
+
+ +
+
+ ))} +
+
+
+ + )} )}
diff --git a/ui/src/components/rules/forms/QuickEditRuleForm.tsx b/ui/src/components/rules/forms/QuickEditRuleForm.tsx index 64655b25da..ef80cfaf4c 100644 --- a/ui/src/components/rules/forms/QuickEditRuleForm.tsx +++ b/ui/src/components/rules/forms/QuickEditRuleForm.tsx @@ -1,9 +1,12 @@ import { Field, FieldArray, Form, Formik } from 'formik'; import { useState } from 'react'; import { useSelector } from 'react-redux'; +import { twMerge } from 'tailwind-merge'; +import { selectReadonly } from '~/app/meta/metaSlice'; import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice'; import TextButton from '~/components/forms/buttons/TextButton'; import Combobox from '~/components/forms/Combobox'; +import SegmentsPicker from '~/components/forms/SegmentsPicker'; import Loading from '~/components/Loading'; import { updateDistribution, updateRule } from '~/data/api'; import { useError } from '~/data/hooks/error'; @@ -11,7 +14,12 @@ import { useSuccess } from '~/data/hooks/success'; import { DistributionType } from '~/types/Distribution'; import { IEvaluatable, IVariantRollout } from '~/types/Evaluatable'; import { IFlag } from '~/types/Flag'; -import { FilterableSegment, ISegment } from '~/types/Segment'; +import { + FilterableSegment, + ISegment, + segmentOperators, + SegmentOperatorType +} from '~/types/Segment'; import { FilterableVariant } from '~/types/Variant'; import { truncateKey } from '~/utils/helpers'; import { distTypes } from './RuleForm'; @@ -32,8 +40,10 @@ export const validRollout = (rollouts: IVariantRollout[]): boolean => { }; interface RuleFormValues { - segmentKey: string; + segmentKeys: FilterableSegment[]; + segmentKey?: string; rollouts: IVariantRollout[]; + operator: SegmentOperatorType; } export default function QuickEditRuleForm(props: QuickEditRuleFormProps) { @@ -51,19 +61,6 @@ export default function QuickEditRuleForm(props: QuickEditRuleFormProps) { ? DistributionType.Single : DistributionType.Multi; - const [selectedSegment, setSelectedSegment] = - useState(() => { - let selected = segments.find((s) => s.key === rule.segment.key) || null; - if (selected) { - return { - ...selected, - displayValue: selected.name, - filterValue: selected.key - }; - } - return null; - }); - const [selectedVariant, setSelectedVariant] = useState(() => { if (ruleType !== DistributionType.Single) return null; @@ -83,13 +80,24 @@ export default function QuickEditRuleForm(props: QuickEditRuleFormProps) { return null; }); + const readOnly = useSelector(selectReadonly); + const handleSubmit = async (values: RuleFormValues) => { - if (rule.segment.key !== values.segmentKey) { + const originalRuleSegments = rule.segments.map((s) => s.key); + const comparableRuleSegments = values.segmentKeys.map((s) => s.key); + + const segmentsDidntChange = + comparableRuleSegments.every((rs) => { + return originalRuleSegments.includes(rs); + }) && comparableRuleSegments.length === originalRuleSegments.length; + + if (!segmentsDidntChange || values.operator !== rule.operator) { // update segment if changed try { await updateRule(namespace.key, flag.key, rule.id, { rank: rule.rank, - segmentKey: values.segmentKey + segmentKeys: comparableRuleSegments, + segmentOperator: values.operator }); } catch (err) { setError(err as Error); @@ -149,16 +157,25 @@ export default function QuickEditRuleForm(props: QuickEditRuleFormProps) { return ( ({ + ...segment, + displayValue: segment.name, + filterValue: segment.key + })), // variantId: rule.rollouts[0].distribution.variantId, - rollouts: rule.rollouts + rollouts: rule.rollouts, + operator: rule.operator }} validate={(values) => { const errors: any = {}; if (!validRollout(values.rollouts)) { errors.rollouts = 'Rollouts must add up to 100%'; } + if (values.segmentKeys.length <= 0) { + errors.segmentKeys = 'Segments length must be greater than 0'; + } return errors; }} onSubmit={(values, { setSubmitting }) => { @@ -184,25 +201,81 @@ export default function QuickEditRuleForm(props: QuickEditRuleFormProps) {
- - id="segmentKey" - name="segmentKey" - placeholder="Select or search for a segment" - values={segments.map((s) => ({ - ...s, - filterValue: truncateKey(s.key), - displayValue: s.name - }))} - selected={selectedSegment} - setSelected={setSelectedSegment} - /> +
+ ( + + arrayHelpers.push(segment) + } + segmentRemove={(index: number) => + arrayHelpers.remove(index) + } + segmentReplace={( + index: number, + segment: FilterableSegment + ) => arrayHelpers.replace(index, segment)} + selectedSegments={formik.values.segmentKeys} + /> + )} + /> +
+
+ {formik.values.segmentKeys.length > 1 && + segmentOperators.map((segmentOperator, index) => ( +
+
+ { + formik.setFieldValue( + 'operator', + segmentOperator.id + ); + }} + checked={ + segmentOperator.id === formik.values.operator + } + value={segmentOperator.id} + disabled={readOnly} + title={ + readOnly + ? 'Not allowed in Read-Only mode' + : undefined + } + /> +
+
+ +
+
+ ))} +
@@ -269,7 +342,7 @@ export default function QuickEditRuleForm(props: QuickEditRuleFormProps) { }))} selected={selectedVariant} setSelected={setSelectedVariant} - disabled + disabled={readOnly} />
@@ -309,7 +382,13 @@ export default function QuickEditRuleForm(props: QuickEditRuleFormProps) {
diff --git a/ui/src/components/rules/forms/RuleForm.tsx b/ui/src/components/rules/forms/RuleForm.tsx index 43899f9aca..9495473591 100644 --- a/ui/src/components/rules/forms/RuleForm.tsx +++ b/ui/src/components/rules/forms/RuleForm.tsx @@ -1,13 +1,13 @@ import { Dialog } from '@headlessui/react'; import { XMarkIcon } from '@heroicons/react/24/outline'; -import { Form, Formik } from 'formik'; +import { FieldArray, Form, Formik } from 'formik'; import { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; import * as Yup from 'yup'; import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice'; import Button from '~/components/forms/buttons/Button'; -import Combobox from '~/components/forms/Combobox'; +import SegmentsPicker from '~/components/forms/SegmentsPicker'; import Loading from '~/components/Loading'; import MoreInfo from '~/components/MoreInfo'; import { createDistribution, createRule } from '~/data/api'; @@ -16,7 +16,12 @@ import { useSuccess } from '~/data/hooks/success'; import { keyValidation } from '~/data/validations'; import { DistributionType, IDistributionVariant } from '~/types/Distribution'; import { IFlag } from '~/types/Flag'; -import { FilterableSegment, ISegment } from '~/types/Segment'; +import { + FilterableSegment, + ISegment, + segmentOperators, + SegmentOperatorType +} from '~/types/Segment'; import { FilterableVariant } from '~/types/Variant'; import { truncateKey } from '~/utils/helpers'; import MultiDistributionFormInputs from './MultiDistributionForm'; @@ -72,6 +77,11 @@ type RuleFormProps = { segments: ISegment[]; }; +interface Segment { + segmentKeys: FilterableSegment[]; + operator: SegmentOperatorType; +} + export default function RuleForm(props: RuleFormProps) { const { setOpen, onSuccess, flag, rank, segments } = props; @@ -84,8 +94,6 @@ export default function RuleForm(props: RuleFormProps) { const [ruleType, setRuleType] = useState(DistributionType.None); - const [selectedSegment, setSelectedSegment] = - useState(null); const [selectedVariant, setSelectedVariant] = useState(null); @@ -99,6 +107,8 @@ export default function RuleForm(props: RuleFormProps) { })); }); + const initialSegmentKeys: FilterableSegment[] = []; + useEffect(() => { if ( ruleType === DistributionType.Multi && @@ -111,13 +121,14 @@ export default function RuleForm(props: RuleFormProps) { } }, [distributions, ruleType]); - const handleSubmit = async () => { - if (!selectedSegment) { - throw new Error('No segment selected'); + const handleSubmit = async (values: Segment) => { + if (values.segmentKeys.length === 0) { + throw new Error('No segments selected'); } const rule = await createRule(namespace.key, flag.key, { - segmentKey: selectedSegment.key, + segmentKeys: values.segmentKeys.map((s) => s.key), + segmentOperator: values.operator, rank }); @@ -142,13 +153,21 @@ export default function RuleForm(props: RuleFormProps) { return ( { - handleSubmit() + onSubmit={(values, { setSubmitting }) => { + handleSubmit(values) .then(() => { onSuccess(); clearError(); @@ -200,19 +219,79 @@ export default function RuleForm(props: RuleFormProps) {
- - id="segmentKey" - name="segmentKey" - placeholder="Select or search for a segment" - values={segments.map((s) => ({ - ...s, - filterValue: truncateKey(s.key), - displayValue: s.name - }))} - selected={selectedSegment} - setSelected={setSelectedSegment} + ( + + arrayHelpers.push(segment) + } + segmentRemove={(index: number) => + arrayHelpers.remove(index) + } + segmentReplace={( + index: number, + segment: FilterableSegment + ) => arrayHelpers.replace(index, segment)} + selectedSegments={formik.values.segmentKeys} + /> + )} />
+ {formik.values.segmentKeys.length > 1 && ( + <> +
+ +
+
+
+
+ {formik.values.segmentKeys.length > 1 && + segmentOperators.map((segmentOperator, index) => ( +
+
+ { + formik.setFieldValue( + 'operator', + segmentOperator.id + ); + }} + checked={ + segmentOperator.id === + formik.values.operator + } + value={segmentOperator.id} + /> +
+
+ +
+
+ ))} +
+
+
+ + )}
diff --git a/ui/src/types/Evaluatable.ts b/ui/src/types/Evaluatable.ts index 415c05ae55..9665ca3031 100644 --- a/ui/src/types/Evaluatable.ts +++ b/ui/src/types/Evaluatable.ts @@ -1,5 +1,5 @@ import { IDistribution } from './Distribution'; -import { ISegment } from './Segment'; +import { ISegment, SegmentOperatorType } from './Segment'; import { IVariant } from './Variant'; export interface IVariantRollout { @@ -9,7 +9,9 @@ export interface IVariantRollout { export interface IEvaluatable { id: string; - segment: ISegment; + segment?: ISegment; + segments: ISegment[]; + operator: SegmentOperatorType; rank: number; rollouts: IVariantRollout[]; createdAt: string; diff --git a/ui/src/types/Rollout.ts b/ui/src/types/Rollout.ts index 98ca438758..7d728c1e46 100644 --- a/ui/src/types/Rollout.ts +++ b/ui/src/types/Rollout.ts @@ -1,4 +1,5 @@ import { IPageable } from './Pageable'; +import { SegmentOperatorType } from './Segment'; export enum RolloutType { SEGMENT = 'SEGMENT_ROLLOUT_TYPE', @@ -17,7 +18,9 @@ export function rolloutTypeToLabel(rolloutType: RolloutType): string { } export interface IRolloutRuleSegment { - segmentKey: string; + segmentOperator?: SegmentOperatorType; + segmentKey?: string; + segmentKeys?: string[]; value: boolean; } diff --git a/ui/src/types/Rule.ts b/ui/src/types/Rule.ts index bd96d27737..2789bdcb51 100644 --- a/ui/src/types/Rule.ts +++ b/ui/src/types/Rule.ts @@ -1,8 +1,11 @@ +import { SegmentOperatorType } from '~/types/Segment'; import { IDistribution } from './Distribution'; import { IPageable } from './Pageable'; export interface IRuleBase { - segmentKey: string; + segmentKey?: string; + segmentKeys?: string[]; + segmentOperator?: SegmentOperatorType; rank: number; } diff --git a/ui/src/types/Segment.ts b/ui/src/types/Segment.ts index 965df1c1ce..87cfcf7030 100644 --- a/ui/src/types/Segment.ts +++ b/ui/src/types/Segment.ts @@ -20,6 +20,24 @@ export enum SegmentMatchType { ANY = 'ANY_MATCH_TYPE' } +export enum SegmentOperatorType { + OR = 'OR_SEGMENT_OPERATOR', + AND = 'AND_SEGMENT_OPERATOR' +} + +export const segmentOperators = [ + { + id: SegmentOperatorType.OR, + name: 'OR', + meta: '(ANY Segment)' + }, + { + id: SegmentOperatorType.AND, + name: 'AND', + meta: '(ALL Segments)' + } +]; + export function segmentMatchTypeToLabel(matchType: SegmentMatchType): string { switch (matchType) { case SegmentMatchType.ALL: diff --git a/ui/tests/rules.spec.ts b/ui/tests/rules.spec.ts index c6c771aea1..c54e0b017c 100644 --- a/ui/tests/rules.spec.ts +++ b/ui/tests/rules.spec.ts @@ -40,9 +40,8 @@ test.describe('Rules', () => { await page.getByRole('link', { name: 'test-rule' }).click(); await page.getByRole('link', { name: 'Evaluation' }).click(); await page.getByRole('button', { name: 'New Rule' }).click(); - - await page.getByRole('combobox').type('test-rule'); - await page.getByRole('option', { name: 'test-rule' }).click(); + await page.locator('#segmentKey-0-select-button').click(); + await page.getByLabel('New Rule').getByText('Test Rule').click(); await page.getByLabel('Multi-Variate').check(); await page.getByRole('button', { name: 'Create' }).click(); await expect(page.getByText('Successfully created rule')).toBeVisible(); @@ -52,7 +51,6 @@ test.describe('Rules', () => { test('can update rule', async ({ page }) => { await page.getByRole('link', { name: 'test-rule' }).click(); await page.getByRole('link', { name: 'Evaluation' }).click(); - await page .locator('input[name="rollouts\\.\\[0\\]\\.distribution\\.rollout"]') .click();