From 848bcf07b8ff4f6a4a056794d521c62535b2e3b8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 9 Apr 2020 13:19:10 -0700 Subject: [PATCH 1/2] Add flow to create simple boolean flag --- browser/flagr-ui/src/components/Flags.vue | 156 +++++++++++----------- docs/api_docs/bundle.yaml | 3 + go.sum | 15 +++ pkg/handler/crud.go | 53 +++++++- pkg/handler/crud_test.go | 39 ++++++ swagger_gen/models/create_flag_request.go | 3 + 6 files changed, 187 insertions(+), 82 deletions(-) diff --git a/browser/flagr-ui/src/components/Flags.vue b/browser/flagr-ui/src/components/Flags.vue index 8d7a77da..b12a6c20 100644 --- a/browser/flagr-ui/src/components/Flags.vue +++ b/browser/flagr-ui/src/components/Flags.vue @@ -11,19 +11,26 @@
- + @@ -34,54 +41,41 @@ placeholder="Search a flag" prefix-icon="el-icon-search" v-model="searchTerm" - v-focus> - + v-focus + > - - - - - - + style="width: 100%" + > + + + - + width="200" + > + width="100" + > @@ -92,87 +86,87 @@ diff --git a/docs/api_docs/bundle.yaml b/docs/api_docs/bundle.yaml index d58ae2bd..12892c3f 100644 --- a/docs/api_docs/bundle.yaml +++ b/docs/api_docs/bundle.yaml @@ -863,6 +863,9 @@ definitions: key: description: unique key representation of the flag type: string + template: + description: template name for creation + type: string putFlagRequest: type: object properties: diff --git a/go.sum b/go.sum index b04432cf..d97fb690 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,7 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f h1:y2hSFdXeA1y5z5f0vfNO0Dg5qVY036qzlz3Pds0B92o= github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 h1:irR1cO6eek3n5uquIVaRAsQmZnlsfPuHNz31cXo4eyk= github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/avast/retry-go v2.2.0+incompatible h1:m+w7mVLWa/oKqX2xYqiEKQQkeGH8DDEXB/XnjS54Wyw= github.com/avast/retry-go v2.2.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= @@ -28,11 +29,13 @@ github.com/aws/aws-sdk-go v1.15.32 h1:tb099RWtGbsXqOWDNKISRyufkdRWOYlXhE4XN0Jm3B github.com/aws/aws-sdk-go v1.15.32/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/brandur/simplebox v0.0.0-20150921201729-84e9865bb03a h1:EMG9wk3iGM7WBAohiKenvpfyh1L5jv3snIMj3ffAMY8= github.com/brandur/simplebox v0.0.0-20150921201729-84e9865bb03a/go.mod h1:8hDWkKEpFQwZcugC69PxsoNQMh+0/A3FzLCppp/yJZM= github.com/bsm/ratelimit v2.0.0+incompatible h1:cV5yEqApIEkLumVjN65y/PlVrzJfCfz+b7BUQrNvCxA= github.com/bsm/ratelimit v2.0.0+incompatible/go.mod h1:CKXgBlwczX35ERUvw2g6Nl+CT0QNd5m+xh3fpzjgbzo= github.com/caarlos0/env v3.3.0+incompatible h1:jCfY0ilpzC2FFViyZyDKCxKybDESTwaR+ebh8zm6AOE= github.com/caarlos0/env v3.3.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= +github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 h1:6/yVvBsKeAw05IUj4AzvrxaCnDjN4nUqKjW9+w5wixg= github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY= @@ -47,12 +50,15 @@ github.com/denisenkom/go-mssqldb v0.0.0-20190418034912-35416408c946/go.mod h1:zA github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/evalphobia/logrus_sentry v0.4.6 h1:825MLGu+SW5H8hMXGeBI7TwX7vgJLd9hz0Eth1Mnp3o= github.com/evalphobia/logrus_sentry v0.4.6/go.mod h1:pKcp+vriitUqu9KiWj/VRFbRfFNUwz95/UkgG8a6MNc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -85,11 +91,13 @@ github.com/go-openapi/swag v0.0.0-20180908172849-dd0dad036e67 h1:HSEYUsQFq79SfgU github.com/go-openapi/swag v0.0.0-20180908172849-dd0dad036e67/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/validate v0.0.0-20180825180342-e0648ff40507 h1:WSEFLFs9bAbxJqnRnZYSYgkQtNjtCjq+/2ai5yR7/QA= github.com/go-openapi/validate v0.0.0-20180825180342-e0648ff40507/go.mod h1:ve8xoSHgqBUifiKgaVbxLmOE0ckvH0oXfsJcnm6SIz0= +github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gohttp/pprof v0.0.0-20141119085724-c9d246cbb3ba h1:OckY4Dk1WhEEEz4zYYMsXG5f6necMtGAyAs19vcpRXk= github.com/gohttp/pprof v0.0.0-20141119085724-c9d246cbb3ba/go.mod h1:V97TX7IXWIioKfmy0IKnnBzsC1jRXP2VicslN9O8IIQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -98,6 +106,7 @@ github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= @@ -123,6 +132,7 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/gorm v0.0.0-20180909231100-123d4f50ef8a h1:Z+fo5W6ecb0uvnWoEtzYoQKB8e9NFHT/19aB9ihFsLM= github.com/jinzhu/gorm v0.0.0-20180909231100-123d4f50ef8a/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= +github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k= github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.0 h1:6WV8LvwPpDhKjo5U9O6b4+xdG/jTXNPwlDme/MTo8Ns= github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc= @@ -148,9 +158,11 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/meatballhat/negroni-logrus v0.0.0-20170801195057-31067281800f h1:V6GHkMOIsnpGDasS1iYiNxEYTY8TmyjQXEF8PqYkKQ8= github.com/meatballhat/negroni-logrus v0.0.0-20170801195057-31067281800f/go.mod h1:Ylx55XGW4gjY7McWT0pgqU0aQquIOChDnYkOVbSuF/c= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -207,13 +219,16 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:s github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/urfave/negroni v0.3.0 h1:PaXOb61mWeZJxc1Ji2xJjpVg9QfPo0rrB+lHyBxGNSU= github.com/urfave/negroni v0.3.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/yadvendar/negroni-newrelic-go-agent v0.0.0-20160803090806-3dc58758cb67 h1:BpDBAgffGUtOwUnYuFVOnl9PuDXW0X7bVw7NX/UdA4w= github.com/yadvendar/negroni-newrelic-go-agent v0.0.0-20160803090806-3dc58758cb67/go.mod h1:eRmB4tpcIoEUfMNyiXTbnZtzfODhBhZB3BIWGDD+vLs= github.com/zhouzhuojie/conditions v0.0.0-20190705160302-784df330cb87 h1:5pQTfWe/n9OvmwOamjhkePoT3dtJv0If1CXl3zkhSZg= github.com/zhouzhuojie/conditions v0.0.0-20190705160302-784df330cb87/go.mod h1:Izhy98HD3MkfwGPz+p9ZV2JuqrpbHjaQbUq9iZHh+ZY= diff --git a/pkg/handler/crud.go b/pkg/handler/crud.go index ff330fd2..459f73c5 100644 --- a/pkg/handler/crud.go +++ b/pkg/handler/crud.go @@ -127,12 +127,60 @@ func (c *crud) CreateFlag(params flag.CreateFlagParams) middleware.Responder { } f.Key = key } - err := getDB().Create(f).Error - if err != nil { + + tx := getDB().Begin() + + if err := tx.Create(f).Error; err != nil { + tx.Rollback() return flag.NewCreateFlagDefault(500).WithPayload( ErrorMessage("cannot create flag. %s", err)) } + if params.Body.Template == "simple" { + // Create our default segment + s := &entity.Segment{} + s.FlagID = f.ID + s.RolloutPercent = uint(100) + s.Rank = entity.SegmentDefaultRank + + if err := tx.Create(s).Error; err != nil { + tx.Rollback() + return flag.NewCreateFlagDefault(500).WithPayload(ErrorMessage("%s", err)) + } + + // .. and our default Variant + v := &entity.Variant{} + v.FlagID = f.ID + v.Key = "on" + + if err := tx.Create(v).Error; err != nil { + tx.Rollback() + return flag.NewCreateFlagDefault(500).WithPayload(ErrorMessage("%s", err)) + } + + // .. and our default Distribution + d := &entity.Distribution{} + d.SegmentID = s.ID + d.VariantID = v.ID + d.VariantKey = v.Key + d.Percent = uint(100) + + if err := tx.Create(d).Error; err != nil { + tx.Rollback() + return flag.NewCreateFlagDefault(500).WithPayload(ErrorMessage("%s", err)) + } + + s.Distributions = append(s.Distributions, *d) + f.Variants = append(f.Variants, *v) + f.Segments = append(f.Segments, *s) + } + + err := tx.Commit().Error + if err != nil { + tx.Rollback() + return flag.NewCreateFlagDefault(500).WithPayload(ErrorMessage("%s", err)) + } + resp := flag.NewCreateFlagOK() payload, err := e2rMapFlag(f) if err != nil { @@ -142,6 +190,7 @@ func (c *crud) CreateFlag(params flag.CreateFlagParams) middleware.Responder { resp.SetPayload(payload) entity.SaveFlagSnapshot(getDB(), f.ID, getSubjectFromRequest(params.HTTPRequest)) + return resp } diff --git a/pkg/handler/crud_test.go b/pkg/handler/crud_test.go index 9c17dabe..ea084841 100644 --- a/pkg/handler/crud_test.go +++ b/pkg/handler/crud_test.go @@ -40,6 +40,45 @@ func TestCrudFlags(t *testing.T) { }) assert.NotZero(t, res.(*flag.CreateFlagOK).Payload.ID) assert.Equal(t, "some_random_flag_key", res.(*flag.CreateFlagOK).Payload.Key) + + flagID := uint(res.(*flag.CreateFlagOK).Payload.ID) + segment := entity.Segment{FlagID: flagID} + db.First(&segment) + assert.Zero(t, segment.ID) + }) + + t.Run("it should be able to create one simple flag", func(t *testing.T) { + res = c.CreateFlag(flag.CreateFlagParams{ + Body: &models.CreateFlagRequest{ + Description: util.StringPtr("simple flag"), + Key: "simple_flag_key", + Template: "simple", + }, + }) + res := res.(*flag.CreateFlagOK) + assert.NotZero(t, res.Payload.ID) + assert.Equal(t, "simple_flag_key", res.Payload.Key) + assert.Equal(t, len(res.Payload.Variants), 1) + assert.Equal(t, len(res.Payload.Segments), 1) + assert.Equal(t, len(res.Payload.Segments[0].Distributions), 1) + flagID := uint(res.Payload.ID) + segment := entity.Segment{FlagID: flagID} + db.First(&segment) + assert.NotZero(t, segment.ID) + assert.Equal(t, segment.Rank, entity.SegmentDefaultRank) + + variant := entity.Variant{FlagID: flagID} + db.First(&variant) + assert.NotZero(t, variant.ID) + assert.Equal(t, variant.Key, "on") + + distribution := entity.Distribution{VariantID: variant.ID} + db.First(&distribution) + assert.NotZero(t, distribution.ID) + assert.Equal(t, distribution.Percent, uint(100)) + assert.Equal(t, distribution.SegmentID, segment.ID) + assert.Equal(t, distribution.VariantKey, variant.Key) + }) t.Run("it should be able to find some flags after creation", func(t *testing.T) { diff --git a/swagger_gen/models/create_flag_request.go b/swagger_gen/models/create_flag_request.go index e76c23c5..2cccf5e6 100644 --- a/swagger_gen/models/create_flag_request.go +++ b/swagger_gen/models/create_flag_request.go @@ -24,6 +24,9 @@ type CreateFlagRequest struct { // unique key representation of the flag Key string `json:"key,omitempty"` + + // template for flag creation - "simple" is the only valid option currently + Template string `json:type,omitempty` } // Validate validates this create flag request From 19d9940d0eda0cdecb7bd8203f5936c42d342224 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 10 Apr 2020 10:55:04 -0700 Subject: [PATCH 2/2] Refactor flag creation and rename simple -> simple_boolean_flag --- browser/flagr-ui/src/components/Flags.vue | 6 +- docs/api_docs/bundle.yaml | 2 +- pkg/handler/crud.go | 80 -------------- pkg/handler/crud_flag_creation.go | 101 ++++++++++++++++++ pkg/handler/crud_flag_creation_test.go | 123 ++++++++++++++++++++++ pkg/handler/crud_test.go | 84 +-------------- swagger/index.yaml | 3 + swagger_gen/models/create_flag_request.go | 4 +- swagger_gen/restapi/embedded_spec.go | 8 ++ 9 files changed, 246 insertions(+), 165 deletions(-) create mode 100644 pkg/handler/crud_flag_creation.go create mode 100644 pkg/handler/crud_flag_creation_test.go diff --git a/browser/flagr-ui/src/components/Flags.vue b/browser/flagr-ui/src/components/Flags.vue index b12a6c20..08f4d7d6 100644 --- a/browser/flagr-ui/src/components/Flags.vue +++ b/browser/flagr-ui/src/components/Flags.vue @@ -26,7 +26,7 @@ Create New Flag Create Simple Boolean Flag @@ -142,8 +142,8 @@ export default { this.$router.push({ name: "flag", params: { flagId: row.id } }); }, onCommandDropdown(command) { - if (command === "create-simple-flag") { - this.createFlag({ template: "simple" }); + if (command === "simple_boolean_flag") { + this.createFlag({ template: command }); } }, createFlag(params) { diff --git a/docs/api_docs/bundle.yaml b/docs/api_docs/bundle.yaml index 12892c3f..ae19bcb1 100644 --- a/docs/api_docs/bundle.yaml +++ b/docs/api_docs/bundle.yaml @@ -864,7 +864,7 @@ definitions: description: unique key representation of the flag type: string template: - description: template name for creation + description: template for flag creation type: string putFlagRequest: type: object diff --git a/pkg/handler/crud.go b/pkg/handler/crud.go index 459f73c5..727e79e7 100644 --- a/pkg/handler/crud.go +++ b/pkg/handler/crud.go @@ -114,86 +114,6 @@ func (c *crud) FindFlags(params flag.FindFlagsParams) middleware.Responder { return resp } -func (c *crud) CreateFlag(params flag.CreateFlagParams) middleware.Responder { - f := &entity.Flag{} - if params.Body != nil { - f.Description = util.SafeString(params.Body.Description) - f.CreatedBy = getSubjectFromRequest(params.HTTPRequest) - - key, err := entity.CreateFlagKey(params.Body.Key) - if err != nil { - return flag.NewCreateFlagDefault(400).WithPayload( - ErrorMessage("cannot create flag. %s", err)) - } - f.Key = key - } - - tx := getDB().Begin() - - if err := tx.Create(f).Error; err != nil { - tx.Rollback() - return flag.NewCreateFlagDefault(500).WithPayload( - ErrorMessage("cannot create flag. %s", err)) - } - - if params.Body.Template == "simple" { - // Create our default segment - s := &entity.Segment{} - s.FlagID = f.ID - s.RolloutPercent = uint(100) - s.Rank = entity.SegmentDefaultRank - - if err := tx.Create(s).Error; err != nil { - tx.Rollback() - return flag.NewCreateFlagDefault(500).WithPayload(ErrorMessage("%s", err)) - } - - // .. and our default Variant - v := &entity.Variant{} - v.FlagID = f.ID - v.Key = "on" - - if err := tx.Create(v).Error; err != nil { - tx.Rollback() - return flag.NewCreateFlagDefault(500).WithPayload(ErrorMessage("%s", err)) - } - - // .. and our default Distribution - d := &entity.Distribution{} - d.SegmentID = s.ID - d.VariantID = v.ID - d.VariantKey = v.Key - d.Percent = uint(100) - - if err := tx.Create(d).Error; err != nil { - tx.Rollback() - return flag.NewCreateFlagDefault(500).WithPayload(ErrorMessage("%s", err)) - } - - s.Distributions = append(s.Distributions, *d) - f.Variants = append(f.Variants, *v) - f.Segments = append(f.Segments, *s) - } - - err := tx.Commit().Error - if err != nil { - tx.Rollback() - return flag.NewCreateFlagDefault(500).WithPayload(ErrorMessage("%s", err)) - } - - resp := flag.NewCreateFlagOK() - payload, err := e2rMapFlag(f) - if err != nil { - return flag.NewCreateFlagDefault(500).WithPayload( - ErrorMessage("cannot map flag. %s", err)) - } - resp.SetPayload(payload) - - entity.SaveFlagSnapshot(getDB(), f.ID, getSubjectFromRequest(params.HTTPRequest)) - - return resp -} - func (c *crud) GetFlag(params flag.GetFlagParams) middleware.Responder { f := &entity.Flag{} result := entity.PreloadSegmentsVariants(getDB()).First(f, params.FlagID) diff --git a/pkg/handler/crud_flag_creation.go b/pkg/handler/crud_flag_creation.go new file mode 100644 index 00000000..2f802e02 --- /dev/null +++ b/pkg/handler/crud_flag_creation.go @@ -0,0 +1,101 @@ +package handler + +import ( + "github.com/checkr/flagr/pkg/entity" + "github.com/checkr/flagr/pkg/util" + "github.com/checkr/flagr/swagger_gen/restapi/operations/flag" + "github.com/go-openapi/runtime/middleware" + "github.com/jinzhu/gorm" +) + +func (c *crud) CreateFlag(params flag.CreateFlagParams) middleware.Responder { + f := &entity.Flag{} + if params.Body != nil { + f.Description = util.SafeString(params.Body.Description) + f.CreatedBy = getSubjectFromRequest(params.HTTPRequest) + + key, err := entity.CreateFlagKey(params.Body.Key) + if err != nil { + return flag.NewCreateFlagDefault(400).WithPayload( + ErrorMessage("cannot create flag. %s", err)) + } + f.Key = key + } + + tx := getDB().Begin() + + if err := tx.Create(f).Error; err != nil { + tx.Rollback() + return flag.NewCreateFlagDefault(500).WithPayload( + ErrorMessage("cannot create flag. %s", err)) + } + + if params.Body.Template == "simple_boolean_flag" { + if err := LoadSimpleBooleanFlagTemplate(f, tx); err != nil { + tx.Rollback() + return flag.NewCreateFlagDefault(500).WithPayload( + ErrorMessage("cannot create flag. %s", err)) + } + } else if params.Body.Template != "" { + return flag.NewCreateFlagDefault(400).WithPayload( + ErrorMessage("unknown value for template: %s", params.Body.Template)) + } + + err := tx.Commit().Error + if err != nil { + tx.Rollback() + return flag.NewCreateFlagDefault(500).WithPayload(ErrorMessage("%s", err)) + } + + resp := flag.NewCreateFlagOK() + payload, err := e2rMapFlag(f) + if err != nil { + return flag.NewCreateFlagDefault(500).WithPayload( + ErrorMessage("cannot map flag. %s", err)) + } + resp.SetPayload(payload) + + entity.SaveFlagSnapshot(getDB(), f.ID, getSubjectFromRequest(params.HTTPRequest)) + + return resp +} + +// LoadSimpleBooleanFlagTemplate loads the simple boolean flag template into +// a new flag. It creates a single segment, variant ('on'), and distribution. +func LoadSimpleBooleanFlagTemplate(flag *entity.Flag, tx *gorm.DB) error { + // Create our default segment + s := &entity.Segment{} + s.FlagID = flag.ID + s.RolloutPercent = uint(100) + s.Rank = entity.SegmentDefaultRank + + if err := tx.Create(s).Error; err != nil { + return err + } + + // .. and our default Variant + v := &entity.Variant{} + v.FlagID = flag.ID + v.Key = "on" + + if err := tx.Create(v).Error; err != nil { + return err + } + + // .. and our default Distribution + d := &entity.Distribution{} + d.SegmentID = s.ID + d.VariantID = v.ID + d.VariantKey = v.Key + d.Percent = uint(100) + + if err := tx.Create(d).Error; err != nil { + return err + } + + s.Distributions = append(s.Distributions, *d) + flag.Variants = append(flag.Variants, *v) + flag.Segments = append(flag.Segments, *s) + + return nil +} diff --git a/pkg/handler/crud_flag_creation_test.go b/pkg/handler/crud_flag_creation_test.go new file mode 100644 index 00000000..67b97bd0 --- /dev/null +++ b/pkg/handler/crud_flag_creation_test.go @@ -0,0 +1,123 @@ +package handler + +import ( + "fmt" + "testing" + + "github.com/checkr/flagr/pkg/entity" + "github.com/checkr/flagr/pkg/util" + "github.com/checkr/flagr/swagger_gen/models" + "github.com/checkr/flagr/swagger_gen/restapi/operations/flag" + "github.com/go-openapi/runtime/middleware" + "github.com/prashantv/gostub" + "github.com/stretchr/testify/assert" +) + +func TestCrudCreateFlag(t *testing.T) { + var res middleware.Responder + db := entity.NewTestDB() + c := &crud{} + + defer db.Close() + defer gostub.StubFunc(&getDB, db).Reset() + + t.Run("it should be able to create one flag", func(t *testing.T) { + res = c.CreateFlag(flag.CreateFlagParams{ + Body: &models.CreateFlagRequest{ + Description: util.StringPtr("funny flag"), + Key: "some_random_flag_key", + }, + }) + assert.NotZero(t, res.(*flag.CreateFlagOK).Payload.ID) + assert.Equal(t, "some_random_flag_key", res.(*flag.CreateFlagOK).Payload.Key) + + flagID := uint(res.(*flag.CreateFlagOK).Payload.ID) + segment := entity.Segment{FlagID: flagID} + db.First(&segment) + assert.Zero(t, segment.ID) + }) + + t.Run("it should be able to create simple_boolean_flag template", func(t *testing.T) { + res = c.CreateFlag(flag.CreateFlagParams{ + Body: &models.CreateFlagRequest{ + Description: util.StringPtr("simple flag"), + Key: "simple_boolean_flag_key", + Template: "simple_boolean_flag", + }, + }) + res := res.(*flag.CreateFlagOK) + assert.NotZero(t, res.Payload.ID) + assert.Equal(t, "simple_boolean_flag_key", res.Payload.Key) + assert.Equal(t, len(res.Payload.Variants), 1) + assert.Equal(t, len(res.Payload.Segments), 1) + assert.Equal(t, len(res.Payload.Segments[0].Distributions), 1) + flagID := uint(res.Payload.ID) + segment := entity.Segment{FlagID: flagID} + db.First(&segment) + assert.NotZero(t, segment.ID) + assert.Equal(t, segment.Rank, entity.SegmentDefaultRank) + + variant := entity.Variant{FlagID: flagID} + db.First(&variant) + assert.NotZero(t, variant.ID) + assert.Equal(t, variant.Key, "on") + + distribution := entity.Distribution{VariantID: variant.ID} + db.First(&distribution) + assert.NotZero(t, distribution.ID) + assert.Equal(t, distribution.Percent, uint(100)) + assert.Equal(t, distribution.SegmentID, segment.ID) + assert.Equal(t, distribution.VariantKey, variant.Key) + }) +} + +func TestCrudCreateFlagWithFailures(t *testing.T) { + var res middleware.Responder + db := entity.NewTestDB() + c := &crud{} + + defer db.Close() + defer gostub.StubFunc(&getDB, db).Reset() + + t.Run("CreateFlag - got e2r MapFlag error", func(t *testing.T) { + defer gostub.StubFunc(&e2rMapFlag, nil, fmt.Errorf("e2r MapFlag error")).Reset() + res = c.CreateFlag(flag.CreateFlagParams{ + Body: &models.CreateFlagRequest{ + Description: util.StringPtr("funny flag"), + }, + }) + assert.NotZero(t, res.(*flag.CreateFlagDefault).Payload) + }) + + t.Run("CreateFlag - db generic error", func(t *testing.T) { + db.Error = fmt.Errorf("db generic error") + res = c.CreateFlag(flag.CreateFlagParams{ + Body: &models.CreateFlagRequest{ + Description: util.StringPtr("funny flag"), + }, + }) + assert.NotZero(t, res.(*flag.CreateFlagDefault).Payload) + db.Error = nil + }) + + t.Run("CreateFlag - invalid key error", func(t *testing.T) { + res = c.CreateFlag(flag.CreateFlagParams{ + Body: &models.CreateFlagRequest{ + Description: util.StringPtr(" flag with a space"), + Key: " 1-2-3", // invalid key + }, + }) + assert.NotZero(t, res.(*flag.CreateFlagDefault).Payload) + }) + + t.Run("CreateFlag - invalid template error", func(t *testing.T) { + res = c.CreateFlag(flag.CreateFlagParams{ + Body: &models.CreateFlagRequest{ + Description: util.StringPtr(" flag with a space"), + Key: "invalid_template", + Template: "invalid_template", + }, + }) + assert.NotZero(t, res.(*flag.CreateFlagDefault).Payload) + }) +} diff --git a/pkg/handler/crud_test.go b/pkg/handler/crud_test.go index ea084841..8c7f4368 100644 --- a/pkg/handler/crud_test.go +++ b/pkg/handler/crud_test.go @@ -31,54 +31,11 @@ func TestCrudFlags(t *testing.T) { assert.Len(t, res.(*flag.FindFlagsOK).Payload, 0) }) - t.Run("it should be able to create one flag", func(t *testing.T) { - res = c.CreateFlag(flag.CreateFlagParams{ - Body: &models.CreateFlagRequest{ - Description: util.StringPtr("funny flag"), - Key: "some_random_flag_key", - }, - }) - assert.NotZero(t, res.(*flag.CreateFlagOK).Payload.ID) - assert.Equal(t, "some_random_flag_key", res.(*flag.CreateFlagOK).Payload.Key) - - flagID := uint(res.(*flag.CreateFlagOK).Payload.ID) - segment := entity.Segment{FlagID: flagID} - db.First(&segment) - assert.Zero(t, segment.ID) - }) - - t.Run("it should be able to create one simple flag", func(t *testing.T) { - res = c.CreateFlag(flag.CreateFlagParams{ - Body: &models.CreateFlagRequest{ - Description: util.StringPtr("simple flag"), - Key: "simple_flag_key", - Template: "simple", - }, - }) - res := res.(*flag.CreateFlagOK) - assert.NotZero(t, res.Payload.ID) - assert.Equal(t, "simple_flag_key", res.Payload.Key) - assert.Equal(t, len(res.Payload.Variants), 1) - assert.Equal(t, len(res.Payload.Segments), 1) - assert.Equal(t, len(res.Payload.Segments[0].Distributions), 1) - flagID := uint(res.Payload.ID) - segment := entity.Segment{FlagID: flagID} - db.First(&segment) - assert.NotZero(t, segment.ID) - assert.Equal(t, segment.Rank, entity.SegmentDefaultRank) - - variant := entity.Variant{FlagID: flagID} - db.First(&variant) - assert.NotZero(t, variant.ID) - assert.Equal(t, variant.Key, "on") - - distribution := entity.Distribution{VariantID: variant.ID} - db.First(&distribution) - assert.NotZero(t, distribution.ID) - assert.Equal(t, distribution.Percent, uint(100)) - assert.Equal(t, distribution.SegmentID, segment.ID) - assert.Equal(t, distribution.VariantKey, variant.Key) - + c.CreateFlag(flag.CreateFlagParams{ + Body: &models.CreateFlagRequest{ + Description: util.StringPtr("funny flag"), + Key: "flag_key_1", + }, }) t.Run("it should be able to find some flags after creation", func(t *testing.T) { @@ -229,37 +186,6 @@ func TestCrudFlagsWithFailures(t *testing.T) { db.Error = nil }) - t.Run("CreateFlag - got e2r MapFlag error", func(t *testing.T) { - defer gostub.StubFunc(&e2rMapFlag, nil, fmt.Errorf("e2r MapFlag error")).Reset() - res = c.CreateFlag(flag.CreateFlagParams{ - Body: &models.CreateFlagRequest{ - Description: util.StringPtr("funny flag"), - }, - }) - assert.NotZero(t, res.(*flag.CreateFlagDefault).Payload) - }) - - t.Run("CreateFlag - db generic error", func(t *testing.T) { - db.Error = fmt.Errorf("db generic error") - res = c.CreateFlag(flag.CreateFlagParams{ - Body: &models.CreateFlagRequest{ - Description: util.StringPtr("funny flag"), - }, - }) - assert.NotZero(t, res.(*flag.CreateFlagDefault).Payload) - db.Error = nil - }) - - t.Run("CreateFlag - invalid key error", func(t *testing.T) { - res = c.CreateFlag(flag.CreateFlagParams{ - Body: &models.CreateFlagRequest{ - Description: util.StringPtr(" flag with a space"), - Key: " 1-2-3", // invalid key - }, - }) - assert.NotZero(t, res.(*flag.CreateFlagDefault).Payload) - }) - t.Run("PutFlag - try to update a non-existing flag", func(t *testing.T) { res = c.PutFlag(flag.PutFlagParams{ FlagID: int64(99999), diff --git a/swagger/index.yaml b/swagger/index.yaml index cffa2b6d..caf10abc 100644 --- a/swagger/index.yaml +++ b/swagger/index.yaml @@ -143,6 +143,9 @@ definitions: key: description: unique key representation of the flag type: string + template: + description: template for flag creation + type: string putFlagRequest: type: object properties: diff --git a/swagger_gen/models/create_flag_request.go b/swagger_gen/models/create_flag_request.go index 2cccf5e6..a444e9aa 100644 --- a/swagger_gen/models/create_flag_request.go +++ b/swagger_gen/models/create_flag_request.go @@ -25,8 +25,8 @@ type CreateFlagRequest struct { // unique key representation of the flag Key string `json:"key,omitempty"` - // template for flag creation - "simple" is the only valid option currently - Template string `json:type,omitempty` + // template for flag creation + Template string `json:"template,omitempty"` } // Validate validates this create flag request diff --git a/swagger_gen/restapi/embedded_spec.go b/swagger_gen/restapi/embedded_spec.go index 3127de7d..78d66df2 100644 --- a/swagger_gen/restapi/embedded_spec.go +++ b/swagger_gen/restapi/embedded_spec.go @@ -1227,6 +1227,10 @@ func init() { "key": { "description": "unique key representation of the flag", "type": "string" + }, + "template": { + "description": "template for flag creation", + "type": "string" } } }, @@ -3000,6 +3004,10 @@ func init() { "key": { "description": "unique key representation of the flag", "type": "string" + }, + "template": { + "description": "template for flag creation", + "type": "string" } } },