diff --git a/backend/builds/ghactions/client.go b/backend/builds/ghactions/client.go index 647197571c..f87223c57e 100644 --- a/backend/builds/ghactions/client.go +++ b/backend/builds/ghactions/client.go @@ -56,6 +56,10 @@ func (c *Client) ListWorkflowRuns(ctx context.Context, owner, repo string) (*Wor } defer res.Body.Close() + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Upstream fetch failed with status %d %s", res.StatusCode, res.Status) + } + var resData WorkflowRunsListResponse if err := json.NewDecoder(res.Body).Decode(&resData); err != nil { return nil, err @@ -76,6 +80,13 @@ func (c *Client) GetWorkflowRun(ctx context.Context, owner, repo, runId string) } defer res.Body.Close() + if res.StatusCode != http.StatusOK { + if res.StatusCode == http.StatusNotFound { + return nil, nil + } + return nil, fmt.Errorf("Upstream fetch failed with status %d %s", res.StatusCode, res.Status) + } + var resData WorkflowRunResponse if err := json.NewDecoder(res.Body).Decode(&resData); err != nil { return nil, err diff --git a/backend/builds/service/service.go b/backend/builds/service/service.go index 1b10fda980..d792704242 100644 --- a/backend/builds/service/service.go +++ b/backend/builds/service/service.go @@ -55,6 +55,9 @@ func (s *service) GetBuild(ctx context.Context, req *buildsv1.GetBuildRequest) ( if err != nil { return nil, status.Errorf(codes.Internal, "Failed to fetch workflow run for %s/%s/%s, %s", owner, repo, runID, err) } + if run == nil { + return nil, status.Errorf(codes.NotFound, "Workflow run %s/%s/%s not found", owner, repo, runID) + } return &buildsv1.GetBuildReply{ Build: s.transformBuild(owner, repo, run), @@ -96,6 +99,7 @@ func (s *service) transformBuild(owner, repo string, run *ghactions.WorkflowRunR Uri: fmt.Sprintf("entity:build:%s/%s/%d", owner, repo, run.ID), CommitId: run.HeadCommit.ID, Message: run.HeadCommit.Message, + Branch: run.HeadBranch, Status: stat, } } @@ -108,7 +112,7 @@ func (s *service) parseBuildURI(uri string) (owner, repo, runID string, err erro } match := entityURIRegex.FindStringSubmatch(uri) - if err != nil { + if match == nil { return "", "", "", fmt.Errorf("uri does not match") } diff --git a/backend/inventory/app/server.go b/backend/inventory/app/server.go index 1d2f94a668..7b208fd42d 100644 --- a/backend/inventory/app/server.go +++ b/backend/inventory/app/server.go @@ -22,9 +22,9 @@ func (s *Server) ListEntities(ctx context.Context, req *pb.ListEntitiesRequest) return nil, status.Error(codes.Internal, "could not list entities") } - result := make([]*pb.Entity, len(entities)) - for i, v := range entities { - result[i] = &pb.Entity{Uri: v} + var result []*pb.Entity + for _, v := range entities { + result = append(result, &pb.Entity{Uri: v}) } return &pb.ListEntitiesReply{Entities: result}, nil diff --git a/backend/inventory/app/server_test.go b/backend/inventory/app/server_test.go index 3bd6e95dd9..5cb46c06e2 100644 --- a/backend/inventory/app/server_test.go +++ b/backend/inventory/app/server_test.go @@ -13,12 +13,14 @@ import ( pb "github.com/spotify/backstage/proto/inventory/v1" ) +var entityURI = "boss://test/test" + func TestServerListEntities(t *testing.T) { testStorage := NewTestStorage() defer testStorage.Close() s := Server{Storage: testStorage.Storage} - entity := &pb.Entity{Uri: "boss://test/test"} + entity := &pb.Entity{Uri: entityURI} _, err := s.CreateEntity(context.Background(), &pb.CreateEntityRequest{Entity: entity}) if err != nil { @@ -32,7 +34,7 @@ func TestServerListEntities(t *testing.T) { if len(list.GetEntities()) != 1 { t.Errorf("ServerTest(TestServerListEntities) expected %v items, got %v", 1, len(list.GetEntities())) } - if list.GetEntities()[0].GetUri() != "boss://test/test" { + if list.GetEntities()[0].GetUri() != entityURI { t.Errorf("ServerTest(TestServerListEntities) expected uri %v, got %v", "boss://test/test", list.GetEntities()[0].GetUri()) } @@ -50,7 +52,7 @@ func TestServerCreateEntity(t *testing.T) { defer testStorage.Close() s := Server{Storage: testStorage.Storage} - entity := &pb.Entity{Uri: "boss://test/test"} + entity := &pb.Entity{Uri: entityURI} createReq := &pb.CreateEntityRequest{Entity: entity} resp, err := s.CreateEntity(context.Background(), createReq) if err != nil { @@ -62,93 +64,97 @@ func TestServerCreateEntity(t *testing.T) { } func TestServerGetEntity(t *testing.T) { - testStorage := NewTestStorage() - defer testStorage.Close() - s := Server{Storage: testStorage.Storage} + t.Run("Get entity", func(t *testing.T) { + testStorage := NewTestStorage() + defer testStorage.Close() + s := Server{Storage: testStorage.Storage} - entity := &pb.Entity{Uri: "boss://test/test"} - createReq := &pb.CreateEntityRequest{Entity: entity} - s.CreateEntity(context.Background(), createReq) + entity := &pb.Entity{Uri: entityURI} + createReq := &pb.CreateEntityRequest{Entity: entity} + s.CreateEntity(context.Background(), createReq) - req := &pb.GetEntityRequest{Entity: entity} - resp, err := s.GetEntity(context.Background(), req) - if err != nil { - t.Errorf("ServerTest(GetEntity) got unexpected error %v", err) - } - if resp == nil { - t.Errorf("ServerTest(GetEntity) returned nil") - } - if resp.GetEntity().GetUri() != entity.GetUri() { - t.Errorf("ServerTest(GetEntity) got %v, wanted %v", resp.GetEntity().GetUri(), entity.GetUri()) - } -} + req := &pb.GetEntityRequest{Entity: entity} + resp, err := s.GetEntity(context.Background(), req) + if err != nil { + t.Errorf("ServerTest(GetEntity) got unexpected error %v", err) + } + if resp == nil { + t.Errorf("ServerTest(GetEntity) returned nil") + } + if resp.GetEntity().GetUri() != entity.GetUri() { + t.Errorf("ServerTest(GetEntity) got %v, wanted %v", resp.GetEntity().GetUri(), entity.GetUri()) + } + }) -func TestServerGetEntityWithIncludedFacts(t *testing.T) { - testStorage := NewTestStorage() - defer testStorage.Close() - s := Server{Storage: testStorage.Storage} + t.Run("Get entity with included facts", func(t *testing.T) { + testStorage := NewTestStorage() + defer testStorage.Close() + s := Server{Storage: testStorage.Storage} + setFactReq := &pb.SetFactRequest{EntityUri: entityURI, Name: "test-name", Value: "test-value"} + s.SetFact(context.Background(), setFactReq) - entityUri := "boss://test/test" - setFactReq := &pb.SetFactRequest{EntityUri: entityUri, Name: "test-name", Value: "test-value"} - s.SetFact(context.Background(), setFactReq) + entity := &pb.Entity{Uri: entityURI} + req := &pb.GetEntityRequest{Entity: entity, IncludeFacts: []string{"test-name"}} - entity := &pb.Entity{Uri: entityUri} - req := &pb.GetEntityRequest{Entity: entity, IncludeFacts: []string{"test-name"}} - - resp, err := s.GetEntity(context.Background(), req) - if err != nil { - t.Errorf("ServerTest(GetEntity) got unexpected error %v", err) - } - if resp == nil { - t.Errorf("ServerTest(GetEntity) returned nil") - } - expectedFacts := []*pb.Fact{{Name: "test-name", Value: "test-value"}} - if !reflect.DeepEqual(resp.GetFacts(), expectedFacts) { - t.Errorf("ServerTest(GetEntity) got %v, wanted %v", resp.GetFacts(), expectedFacts) - } + resp, err := s.GetEntity(context.Background(), req) + if err != nil { + t.Errorf("ServerTest(GetEntity) got unexpected error %v", err) + } + if resp == nil { + t.Errorf("ServerTest(GetEntity) returned nil") + } + expectedFacts := []*pb.Fact{{Name: "test-name", Value: "test-value"}} + if !reflect.DeepEqual(resp.GetFacts(), expectedFacts) { + t.Errorf("ServerTest(GetEntity) got %v, wanted %v", resp.GetFacts(), expectedFacts) + } + }) } func TestServerSetFactForExistingEntity(t *testing.T) { - testStorage := NewTestStorage() - defer testStorage.Close() - s := Server{Storage: testStorage.Storage} + t.Run("Set fact for existing entity", func(t *testing.T) { + testStorage := NewTestStorage() + defer testStorage.Close() + s := Server{Storage: testStorage.Storage} - entity := &pb.Entity{Uri: "boss://test/test"} - createReq := &pb.CreateEntityRequest{Entity: entity} - s.CreateEntity(context.Background(), createReq) + entity := &pb.Entity{Uri: entityURI} + createReq := &pb.CreateEntityRequest{Entity: entity} + s.CreateEntity(context.Background(), createReq) - req := &pb.SetFactRequest{EntityUri: "boss://test/test", Name: "test-name", Value: "test-value"} - resp, err := s.SetFact(context.Background(), req) - if err != nil { - t.Errorf("ServerTest(SetFact) got unexpected error %v", err) - } - if resp == nil { - t.Errorf("ServerTest(SetFact) returned nil") - } - fact := &pb.Fact{Name: req.GetName(), Value: req.GetValue()} - if !reflect.DeepEqual(resp.GetFact(), fact) { - t.Errorf("ServerTest(SetFact) got %v, wanted %v", resp.GetFact(), fact) - } + req := &pb.SetFactRequest{EntityUri: entityURI, Name: "test-name", Value: "test-value"} + resp, err := s.SetFact(context.Background(), req) + if err != nil { + t.Errorf("ServerTest(SetFact) got unexpected error %v", err) + } + if resp == nil { + t.Errorf("ServerTest(SetFact) returned nil") + } + + fact := &pb.Fact{Name: req.GetName(), Value: req.GetValue()} + assertFact(t, resp.GetFact(), fact) + }) + + t.Run("Set fact for non-existing entity", func(t *testing.T) { + testStorage := NewTestStorage() + defer testStorage.Close() + s := Server{Storage: testStorage.Storage} + + req := &pb.SetFactRequest{EntityUri: entityURI, Name: "test-name", Value: "test-value"} + resp, err := s.SetFact(context.Background(), req) + if err != nil { + t.Errorf("ServerTest(SetFact) got unexpected error %v", err) + } + if resp == nil { + t.Errorf("ServerTest(SetFact) returned nil") + } + + fact := &pb.Fact{Name: req.GetName(), Value: req.GetValue()} + assertFact(t, resp.GetFact(), fact) + }) } -func TestServerSetFactForNonExistingEntity(t *testing.T) { - testStorage := NewTestStorage() - defer testStorage.Close() - s := Server{Storage: testStorage.Storage} - - entityUri := "boss://test/test" - req := &pb.SetFactRequest{EntityUri: entityUri, Name: "test-name", Value: "test-value"} - resp, err := s.SetFact(context.Background(), req) - if err != nil { - t.Errorf("ServerTest(SetFact) got unexpected error %v", err) - } - if resp == nil { - t.Errorf("ServerTest(SetFact) returned nil") - } - - fact := &pb.Fact{Name: req.GetName(), Value: req.GetValue()} - if !reflect.DeepEqual(resp.GetFact(), fact) { - t.Errorf("ServerTest(SetFact) got %v, wanted %v", resp.GetFact(), fact) +func assertFact(t *testing.T, got, want *pb.Fact) { + if !reflect.DeepEqual(got, want) { + t.Errorf("ServerTest(SetFact) got %v, wanted %v", got, want) } } diff --git a/backend/proto/builds/v1/builds.pb.go b/backend/proto/builds/v1/builds.pb.go index 2d00732d8b..fded19f883 100644 --- a/backend/proto/builds/v1/builds.pb.go +++ b/backend/proto/builds/v1/builds.pb.go @@ -234,7 +234,8 @@ type Build struct { Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"` CommitId string `protobuf:"bytes,2,opt,name=commit_id,json=commitId,proto3" json:"commit_id,omitempty"` Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` - Status BuildStatus `protobuf:"varint,4,opt,name=status,proto3,enum=spotify.backstage.builds.v1.BuildStatus" json:"status,omitempty"` + Branch string `protobuf:"bytes,4,opt,name=branch,proto3" json:"branch,omitempty"` + Status BuildStatus `protobuf:"varint,5,opt,name=status,proto3,enum=spotify.backstage.builds.v1.BuildStatus" json:"status,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -286,6 +287,13 @@ func (m *Build) GetMessage() string { return "" } +func (m *Build) GetBranch() string { + if m != nil { + return m.Branch + } + return "" +} + func (m *Build) GetStatus() BuildStatus { if m != nil { return m.Status @@ -361,35 +369,36 @@ func init() { func init() { proto.RegisterFile("builds/v1/builds.proto", fileDescriptor_05a627abb7f9adb4) } var fileDescriptor_05a627abb7f9adb4 = []byte{ - // 447 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x41, 0x6f, 0xd3, 0x30, - 0x18, 0x25, 0x6b, 0x97, 0xb6, 0x5f, 0x07, 0x0b, 0x3e, 0x8c, 0x68, 0x13, 0x52, 0xc9, 0xa9, 0x4c, - 0x28, 0x53, 0xcb, 0x05, 0x71, 0x82, 0x75, 0x65, 0xaa, 0xa8, 0x22, 0xe4, 0x2a, 0x17, 0x2e, 0x55, - 0xda, 0x98, 0x62, 0x70, 0x71, 0xb0, 0x9d, 0xa0, 0xfc, 0x09, 0x0e, 0xfc, 0x3c, 0x7e, 0x0d, 0xb2, - 0x9d, 0xa8, 0x11, 0x93, 0xda, 0xde, 0xfc, 0xbe, 0xf7, 0xbd, 0xbc, 0xf7, 0x49, 0x2f, 0x70, 0xb1, - 0xca, 0x29, 0x4b, 0xe5, 0x4d, 0x31, 0xba, 0xb1, 0xaf, 0x30, 0x13, 0x5c, 0x71, 0x74, 0x25, 0x33, - 0xae, 0xe8, 0x97, 0x32, 0x5c, 0x25, 0xeb, 0xef, 0x52, 0x25, 0x1b, 0x12, 0x56, 0x7c, 0x31, 0x0a, - 0xc6, 0xf0, 0x74, 0x4e, 0xa5, 0xba, 0x35, 0x03, 0x4c, 0x7e, 0xe6, 0x44, 0x2a, 0xf4, 0x1c, 0x80, - 0xfc, 0x50, 0x54, 0x95, 0xcb, 0x5c, 0x50, 0xdf, 0x19, 0x38, 0xc3, 0x1e, 0xee, 0xd9, 0x49, 0x2c, - 0x68, 0xc0, 0xe0, 0xbc, 0xa9, 0xc9, 0x58, 0x79, 0x40, 0x81, 0xde, 0x82, 0x6b, 0x2d, 0xfd, 0x93, - 0x41, 0x6b, 0xd8, 0x1f, 0x07, 0xe1, 0x9e, 0x4c, 0xa1, 0xf9, 0x30, 0xae, 0x14, 0x41, 0x08, 0xe7, - 0xf7, 0xc4, 0x9a, 0xd5, 0xf9, 0xae, 0xa0, 0x67, 0xc8, 0x86, 0x59, 0xd7, 0x0c, 0x74, 0xba, 0xdf, - 0x0e, 0x3c, 0xde, 0x09, 0x74, 0xb8, 0x37, 0x70, 0x6a, 0x58, 0xb3, 0x7a, 0x9c, 0xb9, 0x15, 0xa0, - 0x09, 0x74, 0x52, 0xa2, 0x12, 0xca, 0x74, 0x70, 0xad, 0x7d, 0x79, 0x58, 0x7b, 0x67, 0x05, 0xb8, - 0x56, 0x06, 0x7f, 0x1c, 0x38, 0x35, 0x0c, 0xf2, 0xa0, 0xb5, 0x4b, 0xac, 0x9f, 0xfa, 0x92, 0x35, - 0xdf, 0x6e, 0xa9, 0x5a, 0xd2, 0xd4, 0x58, 0xf4, 0x70, 0xd7, 0x0e, 0x66, 0x29, 0xf2, 0xa1, 0xb3, - 0x25, 0x52, 0x26, 0x1b, 0xe2, 0xb7, 0x0c, 0x55, 0x43, 0xf4, 0x0e, 0x5c, 0xa9, 0x12, 0x95, 0x4b, - 0xbf, 0x3d, 0x70, 0x86, 0x4f, 0xc6, 0xc3, 0xc3, 0xb1, 0x16, 0x66, 0x1f, 0x57, 0xba, 0x60, 0x05, - 0x67, 0xcd, 0xb4, 0xe8, 0x02, 0xdc, 0x24, 0x57, 0x5f, 0xb9, 0xa8, 0xd2, 0x55, 0x08, 0xbd, 0x80, - 0x33, 0x5e, 0x10, 0x51, 0x50, 0xf2, 0x6b, 0x99, 0x0b, 0x56, 0x65, 0xec, 0xd7, 0xb3, 0x58, 0x30, - 0xf4, 0x0c, 0x3a, 0x8c, 0x6f, 0x0c, 0x6b, 0x63, 0xba, 0x8c, 0x6f, 0x62, 0xc1, 0xae, 0x3f, 0x42, - 0xbf, 0x61, 0x8d, 0xba, 0xd0, 0x8e, 0xe2, 0xf9, 0xdc, 0x7b, 0x84, 0xfa, 0xd0, 0x59, 0xc4, 0x93, - 0xc9, 0x74, 0xb1, 0xf0, 0x1c, 0x0d, 0x3e, 0xbc, 0x9f, 0xcd, 0x63, 0x3c, 0xf5, 0x4e, 0x34, 0xf8, - 0x34, 0x8d, 0xee, 0x66, 0xd1, 0xbd, 0xd7, 0xd2, 0x00, 0xc7, 0x51, 0xa4, 0x41, 0x7b, 0xfc, 0xd7, - 0x01, 0xd7, 0x36, 0x0e, 0x7d, 0x03, 0xd8, 0xf5, 0x0f, 0x85, 0x7b, 0x6f, 0x7f, 0x50, 0xee, 0xcb, - 0x57, 0x47, 0xef, 0xeb, 0xee, 0xa4, 0xd0, 0xad, 0xcb, 0x84, 0xf6, 0x2b, 0xff, 0x2b, 0xe9, 0xe5, - 0xf5, 0x91, 0xdb, 0x19, 0x2b, 0x6f, 0xe1, 0xb3, 0xed, 0xaf, 0x2c, 0x46, 0x2b, 0xd7, 0xfc, 0xb5, - 0xaf, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x40, 0xf3, 0x96, 0x0b, 0xcf, 0x03, 0x00, 0x00, + // 459 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0xd1, 0x6e, 0xd3, 0x30, + 0x14, 0x25, 0x6b, 0x9b, 0xb6, 0xb7, 0x83, 0x05, 0x3f, 0x8c, 0x68, 0x13, 0x52, 0xc9, 0x53, 0x99, + 0x50, 0xa6, 0x96, 0x17, 0xc4, 0x13, 0xac, 0x2b, 0x53, 0x45, 0x15, 0x21, 0x57, 0x79, 0xe1, 0xa5, + 0x4a, 0x1a, 0xd3, 0x19, 0xdc, 0xb9, 0xd8, 0x4e, 0x50, 0x7f, 0x82, 0x0f, 0xe1, 0x93, 0xf8, 0x1a, + 0x64, 0x3b, 0x51, 0x23, 0x26, 0xb5, 0x7d, 0xf3, 0xb9, 0xe7, 0x9e, 0x9c, 0x73, 0xa5, 0x13, 0x38, + 0x4f, 0x73, 0xca, 0x32, 0x79, 0x5d, 0x0c, 0xaf, 0xed, 0x2b, 0xdc, 0x08, 0xae, 0x38, 0xba, 0x94, + 0x1b, 0xae, 0xe8, 0xb7, 0x6d, 0x98, 0x26, 0xcb, 0x1f, 0x52, 0x25, 0x2b, 0x12, 0x96, 0x7c, 0x31, + 0x0c, 0x46, 0xf0, 0x7c, 0x46, 0xa5, 0xba, 0x31, 0x03, 0x4c, 0x7e, 0xe6, 0x44, 0x2a, 0xf4, 0x12, + 0x80, 0x3c, 0x28, 0xaa, 0xb6, 0x8b, 0x5c, 0x50, 0xdf, 0xe9, 0x3b, 0x83, 0x2e, 0xee, 0xda, 0x49, + 0x2c, 0x68, 0xc0, 0xe0, 0xac, 0xae, 0xd9, 0xb0, 0xed, 0x01, 0x05, 0x7a, 0x0f, 0xae, 0xb5, 0xf4, + 0x4f, 0xfa, 0x8d, 0x41, 0x6f, 0x14, 0x84, 0x7b, 0x32, 0x85, 0xe6, 0xc3, 0xb8, 0x54, 0x04, 0x21, + 0x9c, 0xdd, 0x11, 0x6b, 0x56, 0xe5, 0xbb, 0x84, 0xae, 0x21, 0x6b, 0x66, 0x1d, 0x33, 0xd0, 0xe9, + 0x7e, 0x3b, 0xf0, 0x74, 0x27, 0xd0, 0xe1, 0xde, 0x41, 0xcb, 0xb0, 0x66, 0xf5, 0x38, 0x73, 0x2b, + 0x40, 0x63, 0x68, 0x67, 0x44, 0x25, 0x94, 0xe9, 0xe0, 0x5a, 0xfb, 0xfa, 0xb0, 0xf6, 0xd6, 0x0a, + 0x70, 0xa5, 0x0c, 0xfe, 0x38, 0xd0, 0x32, 0x0c, 0xf2, 0xa0, 0xb1, 0x4b, 0xac, 0x9f, 0xfa, 0x92, + 0x25, 0x5f, 0xaf, 0xa9, 0x5a, 0xd0, 0xcc, 0x58, 0x74, 0x71, 0xc7, 0x0e, 0xa6, 0x19, 0xf2, 0xa1, + 0xbd, 0x26, 0x52, 0x26, 0x2b, 0xe2, 0x37, 0x0c, 0x55, 0x41, 0x74, 0x0e, 0x6e, 0x2a, 0x92, 0x87, + 0xe5, 0xbd, 0xdf, 0x34, 0x44, 0x89, 0xd0, 0x07, 0x70, 0xa5, 0x4a, 0x54, 0x2e, 0xfd, 0x56, 0xdf, + 0x19, 0x3c, 0x1b, 0x0d, 0x0e, 0xc7, 0x9d, 0x9b, 0x7d, 0x5c, 0xea, 0x82, 0x14, 0x4e, 0xeb, 0x57, + 0x68, 0xa7, 0x24, 0x57, 0xf7, 0x5c, 0x94, 0xa9, 0x4b, 0x84, 0x5e, 0xc1, 0x29, 0x2f, 0x88, 0x28, + 0x28, 0xf9, 0xb5, 0xc8, 0x05, 0x2b, 0xb3, 0xf7, 0xaa, 0x59, 0x2c, 0x18, 0x7a, 0x01, 0x6d, 0xc6, + 0x57, 0x86, 0xb5, 0xf1, 0x5d, 0xc6, 0x57, 0xb1, 0x60, 0x57, 0x9f, 0xa1, 0x57, 0xb3, 0x46, 0x1d, + 0x68, 0x46, 0xf1, 0x6c, 0xe6, 0x3d, 0x41, 0x3d, 0x68, 0xcf, 0xe3, 0xf1, 0x78, 0x32, 0x9f, 0x7b, + 0x8e, 0x06, 0x9f, 0x3e, 0x4e, 0x67, 0x31, 0x9e, 0x78, 0x27, 0x1a, 0x7c, 0x99, 0x44, 0xb7, 0xd3, + 0xe8, 0xce, 0x6b, 0x68, 0x80, 0xe3, 0x28, 0xd2, 0xa0, 0x39, 0xfa, 0xeb, 0x80, 0x6b, 0x9b, 0x88, + 0xbe, 0x03, 0xec, 0x7a, 0x89, 0xc2, 0xbd, 0xb7, 0x3f, 0x2a, 0xfd, 0xc5, 0x9b, 0xa3, 0xf7, 0x75, + 0xa7, 0x32, 0xe8, 0x54, 0x25, 0x43, 0xfb, 0x95, 0xff, 0x95, 0xf7, 0xe2, 0xea, 0xc8, 0xed, 0x0d, + 0xdb, 0xde, 0xc0, 0x57, 0xdb, 0x6b, 0x59, 0x0c, 0x53, 0xd7, 0xfc, 0xcd, 0x6f, 0xff, 0x05, 0x00, + 0x00, 0xff, 0xff, 0xc2, 0xff, 0xd7, 0x62, 0xe7, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/frontend/packages/app/src/App.tsx b/frontend/packages/app/src/App.tsx index 1936c63c98..9fbbf28dca 100644 --- a/frontend/packages/app/src/App.tsx +++ b/frontend/packages/app/src/App.tsx @@ -1,18 +1,10 @@ -import { - BackstageTheme, - createApp, - Header, - InfoCard, - Page, - theme, -} from '@backstage/core'; +import { BackstageTheme, createApp, InfoCard } from '@backstage/core'; import HomePagePlugin from '@backstage/plugin-home-page'; //import PageHeader from './components/PageHeader'; import { LoginComponent } from '@backstage/plugin-login'; import { CssBaseline, makeStyles, ThemeProvider } from '@material-ui/core'; import React, { FC } from 'react'; import { BrowserRouter as Router } from 'react-router-dom'; -import HomePageTimer from './components/HomepageTimer'; import SideBar from './components/SideBar'; import entities from './entities'; import { LoginBarrier } from './login/LoginBarrier'; @@ -37,20 +29,10 @@ const useStyles = makeStyles(theme => ({ display: 'grid', // FIXME: Don't used a fixed width here gridTemplateColumns: '64px auto', - gridTemplateRows: 'auto 1fr', + gridTemplateRows: '1fr', width: '100%', height: '100vh', }, - mainContentArea: { - overflowX: 'hidden', - overflowY: 'auto', - }, - pageBody: { - padding: theme.spacing(2), - }, - avatarButton: { - padding: theme.spacing(2), - }, })); const currentUser = new MockCurrentUser(); @@ -69,14 +51,7 @@ const AppShell: FC<{}> = ({ children }) => { return (
- -
-
- -
-
{children}
-
-
+ {children}
); }; diff --git a/frontend/packages/app/src/components/SideBar/SideBar.tsx b/frontend/packages/app/src/components/SideBar/SideBar.tsx index 10913378ba..c3e4e7f082 100644 --- a/frontend/packages/app/src/components/SideBar/SideBar.tsx +++ b/frontend/packages/app/src/components/SideBar/SideBar.tsx @@ -158,7 +158,7 @@ const useStyles = makeStyles(theme => ({ display: 'flex', flexFlow: 'column nowrap', alignItems: 'flex-start', - position: 'absolute', + position: 'fixed', left: 0, top: 0, bottom: 0, diff --git a/frontend/packages/app/src/entities/index.ts b/frontend/packages/app/src/entities/index.ts index 38e529d87f..01f4105902 100644 --- a/frontend/packages/app/src/entities/index.ts +++ b/frontend/packages/app/src/entities/index.ts @@ -4,6 +4,8 @@ import { createEntityPage, } from '@backstage/core'; import ComputerIcon from '@material-ui/icons/Computer'; +import WebIcon from '@material-ui/icons/Web'; +import DnsIcon from '@material-ui/icons/Dns'; import MockEntityPage from './MockEntityPage'; import MockEntityCard from './MockEntityCard'; import GithubActionsPlugin from '@backstage/plugin-github-actions'; @@ -14,9 +16,9 @@ const serviceOverviewPage = createWidgetView() .addComponent(MockEntityCard); const serviceView = createEntityPage() - .addPage('Overview', '/overview', serviceOverviewPage) + .addPage('Overview', WebIcon, '/overview', serviceOverviewPage) .register(GithubActionsPlugin) - .addComponent('Deployment', '/deployment', MockEntityPage); + .addComponent('Deployment', DnsIcon, '/deployment', MockEntityPage); const serviceEntity = createEntityKind({ kind: 'service', diff --git a/frontend/packages/core/package.json b/frontend/packages/core/package.json index d22516afe1..d7265591eb 100644 --- a/frontend/packages/core/package.json +++ b/frontend/packages/core/package.json @@ -4,6 +4,8 @@ "main": "src/index.ts", "main:src": "src/index.ts", "devDependencies": { + "@material-ui/core": "^4.9.1", + "@material-ui/icons": "^4.9.1", "@spotify/web-scripts": "^6.0.0", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", diff --git a/frontend/packages/core/src/api/entity/EntityKind.ts b/frontend/packages/core/src/api/entity/EntityKind.ts index 7f6447f4a6..b10a16c991 100644 --- a/frontend/packages/core/src/api/entity/EntityKind.ts +++ b/frontend/packages/core/src/api/entity/EntityKind.ts @@ -1,10 +1,11 @@ import { ComponentType } from 'react'; import { AppComponentBuilder } from '../app/types'; +import { IconComponent } from '../types'; export type EntityConfig = { kind: string; title: string; - icon: React.ComponentType<{ fontSize: number }>; + icon: IconComponent; color: { primary: string; secondary: string; diff --git a/frontend/packages/core/src/api/entityView/EntityPageBuilder.tsx b/frontend/packages/core/src/api/entityView/EntityPageBuilder.tsx index 8dbf6ba2a0..fdbdef01fb 100644 --- a/frontend/packages/core/src/api/entityView/EntityPageBuilder.tsx +++ b/frontend/packages/core/src/api/entityView/EntityPageBuilder.tsx @@ -1,83 +1,21 @@ -import React, { ComponentType, FC } from 'react'; -import { Route, Redirect, Switch } from 'react-router-dom'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import { AppComponentBuilder, App } from '../app/types'; -import { useEntity, useEntityUri, useEntityConfig } from './EntityContext'; -import EntityLink from '../../components/EntityLink/EntityLink'; +import React, { ComponentType } from 'react'; +import DefaultEntityPage from '../../components/DefaultEntityPage'; +import { App, AppComponentBuilder } from '../app/types'; import BackstagePlugin from '../plugin/Plugin'; +import { EntityPageNavItem, EntityPageView } from './types'; +import { IconComponent } from '../types'; -const EntityLayout: FC<{}> = ({ children }) => { - const config = useEntityConfig(); - return ( -
{children}
- ); -}; - -const EntitySidebar: FC<{}> = ({ children }) => { - return {children}; -}; - -const EntitySidebarItem: FC<{ title: string; path: string }> = ({ - title, - path, -}) => { - const entityUri = useEntityUri(); - - return ( - - - {title} - - - ); -}; - -type EntityPageNavItem = { - title: string; - target: string; -}; - -type EntityPageView = { - path: string; - component: ComponentType; -}; - -type Props = { - navItems: EntityPageNavItem[]; - views: EntityPageView[]; -}; - -const EntityPageComponent: FC = ({ navItems, views }) => { - const { kind, id } = useEntity(); - const basePath = `/entity/${kind}/${id}`; - - return ( - - - {navItems.map(({ title, target }) => ( - - ))} - - - {views.map(({ path, component }) => ( - - ))} - - - - ); -}; +// type AppComponents = { +// EntityPage: ComponentType; +// EntityPageNavbar: ComponentType; +// EntityPageHeader: ComponentType; +// }; type EntityPageRegistration = | { type: 'page'; title: string; + icon: IconComponent; path: string; page: AppComponentBuilder; } @@ -88,6 +26,7 @@ type EntityPageRegistration = | { type: 'component'; title: string; + icon: IconComponent; path: string; component: ComponentType; }; @@ -97,19 +36,27 @@ export default class EntityPageBuilder extends AppComponentBuilder { addPage( title: string, + icon: IconComponent, path: string, page: AppComponentBuilder, ): EntityPageBuilder { - this.registrations.push({ type: 'page', title, path, page }); + this.registrations.push({ type: 'page', title, icon, path, page }); return this; } addComponent( title: string, + icon: IconComponent, path: string, component: ComponentType, ): EntityPageBuilder { - this.registrations.push({ type: 'component', title, path, component }); + this.registrations.push({ + type: 'component', + title, + icon, + path, + component, + }); return this; } @@ -125,14 +72,14 @@ export default class EntityPageBuilder extends AppComponentBuilder { for (const reg of this.registrations) { switch (reg.type) { case 'page': { - const { title, path, page } = reg; - navItems.push({ title, target: path }); + const { title, icon, path, page } = reg; + navItems.push({ title, icon, target: path }); views.push({ path, component: page.build(app) }); break; } case 'component': { - const { title, path, component } = reg; - navItems.push({ title, target: path }); + const { title, icon, path, component } = reg; + navItems.push({ title, icon, target: path }); views.push({ path, component }); break; } @@ -141,8 +88,8 @@ export default class EntityPageBuilder extends AppComponentBuilder { for (const output of reg.plugin.output()) { switch (output.type) { case 'entity-page-nav-item': - const { title, target } = output; - navItems.push({ title, target }); + const { title, icon, target } = output; + navItems.push({ title, icon, target }); added = true; break; case 'entity-page-view-route': @@ -162,6 +109,6 @@ export default class EntityPageBuilder extends AppComponentBuilder { } } - return () => ; + return () => ; } } diff --git a/frontend/packages/core/src/api/entityView/types.ts b/frontend/packages/core/src/api/entityView/types.ts new file mode 100644 index 0000000000..8708057b8a --- /dev/null +++ b/frontend/packages/core/src/api/entityView/types.ts @@ -0,0 +1,24 @@ +import { ComponentType } from 'react'; +import { IconComponent } from '../types'; + +export type EntityPageNavItem = { + icon: IconComponent; + title: string; + target: string; +}; + +export type EntityPageView = { + path: string; + component: ComponentType; +}; + +export type EntityPageProps = { + navItems: EntityPageNavItem[]; + views: EntityPageView[]; +}; + +export type EntityPageNavbarProps = { + navItems: EntityPageNavItem[]; +}; + +export type EntityPageHeaderProps = {}; diff --git a/frontend/packages/core/src/api/plugin/Plugin.tsx b/frontend/packages/core/src/api/plugin/Plugin.tsx index e351801047..772776e288 100644 --- a/frontend/packages/core/src/api/plugin/Plugin.tsx +++ b/frontend/packages/core/src/api/plugin/Plugin.tsx @@ -1,5 +1,6 @@ import { ComponentType } from 'react'; import { PluginOutput, RoutePath, RouteOptions } from './types'; +import { IconComponent } from '../types'; export type PluginConfig = { id: string; @@ -27,6 +28,7 @@ export type RouterHooks = { type EntityPageSidebarItemOptions = { title: string; + icon: IconComponent; target: RoutePath; }; @@ -79,11 +81,12 @@ export default class Plugin { }, }, entityPage: { - navItem({ title, target }) { + navItem({ title, icon, target }) { outputs.push({ type: 'entity-page-nav-item', - target, title, + icon, + target, }); }, route(path, component, options) { diff --git a/frontend/packages/core/src/api/plugin/types.ts b/frontend/packages/core/src/api/plugin/types.ts index ddc6364ec5..f6efee230e 100644 --- a/frontend/packages/core/src/api/plugin/types.ts +++ b/frontend/packages/core/src/api/plugin/types.ts @@ -1,4 +1,5 @@ import { ComponentType } from 'react'; +import { IconComponent } from '../types'; export type RouteOptions = { // Whether the route path must match exactly, defaults to true. @@ -31,6 +32,7 @@ export type EntityPageViewRouteOutput = { export type EntityPageNavItemOutput = { type: 'entity-page-nav-item'; title: string; + icon: IconComponent; target: RoutePath; }; diff --git a/frontend/packages/core/src/api/types.ts b/frontend/packages/core/src/api/types.ts index 06f1570900..8462a5a1b9 100644 --- a/frontend/packages/core/src/api/types.ts +++ b/frontend/packages/core/src/api/types.ts @@ -1,10 +1,5 @@ -export type User = { - id: string; - email: string; -}; +import { ComponentType } from 'react'; -export type UserApi = { - isLoggedIn(): Promise; - - getUser(): Promise; -}; +export type IconComponent = ComponentType<{ + fontSize: 'inherit' | 'default' | 'small' | 'large'; +}>; diff --git a/frontend/packages/core/src/components/DefaultEntityPage/DefaultEntityPage.tsx b/frontend/packages/core/src/components/DefaultEntityPage/DefaultEntityPage.tsx new file mode 100644 index 0000000000..5edbea7bf0 --- /dev/null +++ b/frontend/packages/core/src/components/DefaultEntityPage/DefaultEntityPage.tsx @@ -0,0 +1,62 @@ +import React, { FC } from 'react'; +import { makeStyles, Theme } from '@material-ui/core'; +import { EntityPageProps } from '../../api/entityView/types'; +import { useEntity } from '../../api'; +import { Switch, Route, Redirect } from 'react-router-dom'; +import DefaultEntityPageHeader from '../DefaultEntityPageHeader'; +import DefaultEntityPageNavbar from '../DefaultEntityPageNavbar'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'grid', + gridTemplateAreas: ` + 'header header' + 'navbar content' + `, + gridTemplateRows: 'auto 1fr', + gridTemplateColumns: 'auto 1fr', + minHeight: '100%', + paddingBottom: theme.spacing(3), + }, + header: { + gridArea: 'header', + }, + navbar: { + gridArea: 'navbar', + }, + content: { + gridArea: 'content', + }, +})); + +const DefaultEntityPage: FC = ({ navItems, views }) => { + const classes = useStyles(); + const { kind, id } = useEntity(); + const basePath = `/entity/${kind}/${id}`; + + return ( +
+
+ +
+
+ +
+
+ + {views.map(({ path, component }) => ( + + ))} + + +
+
+ ); +}; + +export default DefaultEntityPage; diff --git a/frontend/packages/core/src/components/DefaultEntityPage/index.ts b/frontend/packages/core/src/components/DefaultEntityPage/index.ts new file mode 100644 index 0000000000..4f683076c4 --- /dev/null +++ b/frontend/packages/core/src/components/DefaultEntityPage/index.ts @@ -0,0 +1 @@ +export { default } from './DefaultEntityPage'; diff --git a/frontend/packages/core/src/components/DefaultEntityPageHeader/DefaultEntityPageHeader.tsx b/frontend/packages/core/src/components/DefaultEntityPageHeader/DefaultEntityPageHeader.tsx new file mode 100644 index 0000000000..b9e2768141 --- /dev/null +++ b/frontend/packages/core/src/components/DefaultEntityPageHeader/DefaultEntityPageHeader.tsx @@ -0,0 +1,18 @@ +import React, { FC } from 'react'; +import { Header, useEntity, useEntityConfig, theme } from '../..'; +import { EntityPageHeaderProps } from '../../api/entityView/types'; +import { Theme } from '../../layout/Page/Page'; + +const DefaultEntityPageHeader: FC = () => { + const { id } = useEntity(); + const config = useEntityConfig(); + + // TODO(rugvip): provide theme through entity config + return ( + +
+ + ); +}; + +export default DefaultEntityPageHeader; diff --git a/frontend/packages/core/src/components/DefaultEntityPageHeader/index.ts b/frontend/packages/core/src/components/DefaultEntityPageHeader/index.ts new file mode 100644 index 0000000000..5c9304bad7 --- /dev/null +++ b/frontend/packages/core/src/components/DefaultEntityPageHeader/index.ts @@ -0,0 +1 @@ +export { default } from './DefaultEntityPageHeader'; diff --git a/frontend/packages/core/src/components/DefaultEntityPageNavbar/DefaultEntityPageNavbar.tsx b/frontend/packages/core/src/components/DefaultEntityPageNavbar/DefaultEntityPageNavbar.tsx new file mode 100644 index 0000000000..69aed8bd9f --- /dev/null +++ b/frontend/packages/core/src/components/DefaultEntityPageNavbar/DefaultEntityPageNavbar.tsx @@ -0,0 +1,36 @@ +import React, { FC } from 'react'; +import { EntityPageNavbarProps } from '../../api/entityView/types'; +import { useEntityUri } from '../..'; +import { List, makeStyles, Theme } from '@material-ui/core'; +import NavbarItem from './NavbarItem'; + +const useStyles = makeStyles({ + nav: { + gridArea: 'pageNav', + width: 220, + transition: 'width 0.07s, height 0s', + transitionTimingFunction: 'ease-in', + backgroundColor: '#eeeeee', + boxShadow: '0px 0 4px 0px rgba(0,0,0,0.35)', + }, + list: { + padding: 0, + }, +}); + +const DefaultEntityPageNavbar: FC = ({ navItems }) => { + const classes = useStyles(); + const entityUri = useEntityUri(); + + return ( + + ); +}; + +export default DefaultEntityPageNavbar; diff --git a/frontend/packages/core/src/components/DefaultEntityPageNavbar/NavbarItem.tsx b/frontend/packages/core/src/components/DefaultEntityPageNavbar/NavbarItem.tsx new file mode 100644 index 0000000000..d75ee16f4c --- /dev/null +++ b/frontend/packages/core/src/components/DefaultEntityPageNavbar/NavbarItem.tsx @@ -0,0 +1,72 @@ +import React, { FC } from 'react'; +import { EntityLink } from '../..'; +import { + ListItem, + makeStyles, + Theme, + ListItemIcon, + ListItemText, + Typography, +} from '@material-ui/core'; +import { EntityPageNavItem } from '../../api/entityView/types'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'block', + overflow: 'hidden', + borderBottom: `1px solid #d9d9d9`, + paddingLeft: theme.spacing(1), + }, + label: { + color: '#333', + fontWeight: 'bolder', + whiteSpace: 'nowrap', + lineHeight: 1.0, + }, + iconImg: { + width: 24, + height: 24, + }, + icon: { + margin: theme.spacing(0.5, 2, 0.5, 0), + minWidth: 0, + fontSize: 24, + }, + expand: { + color: 'white', + }, +})); + +type Props = { + navItem: EntityPageNavItem; + entityUri: string; +}; + +const NavbarItem: FC = ({ navItem, entityUri }) => { + const classes = useStyles(); + const IconComponent = navItem.icon; + + return ( + + + + + + + {navItem.title} + + } + disableTypography + /> + + + ); +}; + +export default NavbarItem; diff --git a/frontend/packages/core/src/components/DefaultEntityPageNavbar/index.ts b/frontend/packages/core/src/components/DefaultEntityPageNavbar/index.ts new file mode 100644 index 0000000000..14a807a40f --- /dev/null +++ b/frontend/packages/core/src/components/DefaultEntityPageNavbar/index.ts @@ -0,0 +1 @@ +export { default } from './DefaultEntityPageNavbar'; diff --git a/frontend/packages/core/src/components/EntityLink/EntityLink.tsx b/frontend/packages/core/src/components/EntityLink/EntityLink.tsx index 04b070e397..57993f8abf 100644 --- a/frontend/packages/core/src/components/EntityLink/EntityLink.tsx +++ b/frontend/packages/core/src/components/EntityLink/EntityLink.tsx @@ -1,17 +1,17 @@ import React, { FC } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, LinkProps } from 'react-router-dom'; -type Props = { +type Props = Omit & { subPath?: string; } & ( - | { - kind: string; - id?: string; - } - | { - uri: string; - } -); + | { + kind: string; + id?: string; + } + | { + uri: string; + } + ); export function buildPath(kind: string, id?: string, subPath?: string) { if (id) { @@ -26,7 +26,11 @@ export function buildPath(kind: string, id?: string, subPath?: string) { const EntityLink: FC = ({ subPath, children, ...props }) => { if ('kind' in props) { const { kind, id } = props; - return {children}; + return ( + + {children} + + ); } else { const match = props.uri.match(/entity:([^:]+)(:[^:]+)?/); if (!match) { @@ -36,7 +40,11 @@ const EntityLink: FC = ({ subPath, children, ...props }) => { const [, kind, maybeId] = match; const id = maybeId ? maybeId.slice(1) : undefined; - return {children}; + return ( + + {children} + + ); } }; diff --git a/frontend/packages/core/src/index.ts b/frontend/packages/core/src/index.ts index a55d9526d7..be8b1e851e 100644 --- a/frontend/packages/core/src/index.ts +++ b/frontend/packages/core/src/index.ts @@ -3,14 +3,14 @@ export { default as EntityLink, RelativeEntityLink, } from './components/EntityLink'; -export { default as Page } from '../src/layout/Page'; -export { gradients, theme } from '../src/layout/Page'; -export { default as Header } from '../src/layout/Header/Header'; -export { default as HeaderLabel } from '../src/layout/HeaderLabel'; -export { default as InfoCard } from '../src/layout/InfoCard'; -export { default as ErrorBoundary } from '../src/layout/ErrorBoundary'; -export { default as BackstageTheme } from '../src/theme/BackstageTheme'; -export { COLORS } from '../src/theme/BackstageTheme'; +export { default as Page } from './layout/Page'; +export { gradients, theme } from './layout/Page'; +export { default as Header } from './layout/Header/Header'; +export { default as HeaderLabel } from './layout/HeaderLabel'; +export { default as InfoCard } from './layout/InfoCard'; +export { default as ErrorBoundary } from './layout/ErrorBoundary'; +export { default as BackstageTheme } from './theme/BackstageTheme'; +export { COLORS } from './theme/BackstageTheme'; export { default as HorizontalScrollGrid } from './components/HorizontalScrollGrid'; export { default as ProgressCard } from './components/ProgressCard'; export { default as CircleProgress } from './components/CircleProgress'; diff --git a/frontend/packages/core/src/layout/Header/Header.js b/frontend/packages/core/src/layout/Header/Header.js index 5ddb7865e4..7f715e5996 100644 --- a/frontend/packages/core/src/layout/Header/Header.js +++ b/frontend/packages/core/src/layout/Header/Header.js @@ -103,7 +103,7 @@ const styles = theme => ({ gridArea: 'pageHeader', padding: theme.spacing(3), minHeight: 118, - width: '100vw', + width: '100%', boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)', position: 'relative', zIndex: 100, diff --git a/frontend/packages/plugins/github-actions/src/apis/builds/BuildsClient.ts b/frontend/packages/plugins/github-actions/src/apis/builds/BuildsClient.ts index d435bffbad..721a9ac006 100644 --- a/frontend/packages/plugins/github-actions/src/apis/builds/BuildsClient.ts +++ b/frontend/packages/plugins/github-actions/src/apis/builds/BuildsClient.ts @@ -52,6 +52,7 @@ export default class BuildsClient { return { commitId: build.getCommitId(), message: build.getMessage(), + branch: build.getBranch(), status: statusTable[build.getStatus()] || BuildStatus.Null, uri: build.getUri(), }; diff --git a/frontend/packages/plugins/github-actions/src/apis/builds/types.ts b/frontend/packages/plugins/github-actions/src/apis/builds/types.ts index 3f4b205047..ee57f00b5a 100644 --- a/frontend/packages/plugins/github-actions/src/apis/builds/types.ts +++ b/frontend/packages/plugins/github-actions/src/apis/builds/types.ts @@ -9,6 +9,7 @@ export enum BuildStatus { export type Build = { commitId: string; message: string; + branch: string; status: BuildStatus; uri: string; }; diff --git a/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx b/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx index a9ba638302..51a693ce31 100644 --- a/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx +++ b/frontend/packages/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx @@ -38,8 +38,8 @@ const BuildDetailsPage: FC = () => { } if (status.error) { return ( - - Failed to load build, {status.error} + + Failed to load build, {status.error.message} ); } @@ -50,6 +50,12 @@ const BuildDetailsPage: FC = () => { + + + Branch + + {details?.build.branch} + Message diff --git a/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx b/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx index 009d1df4ef..9096cc8f2e 100644 --- a/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx +++ b/frontend/packages/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx @@ -10,6 +10,8 @@ import { LinearProgress, Typography, Tooltip, + makeStyles, + Theme, } from '@material-ui/core'; import { RelativeEntityLink } from '@backstage/core'; import { BuildsClient } from '../../apis/builds'; @@ -28,35 +30,51 @@ const LongText: FC<{ text: string; max: number }> = ({ text, max }) => { ); }; +const useStyles = makeStyles(theme => ({ + root: { + padding: theme.spacing(2), + }, + title: { + paddingBottom: theme.spacing(2), + }, +})); + const BuildListPage: FC<{}> = () => { + const classes = useStyles(); const status = useAsync(() => client.listBuilds('entity:spotify:backstage')); + let content: JSX.Element; + if (status.loading) { - return ; - } - if (status.error) { - return ( + content = ; + } else if (status.error) { + content = ( - Failed to load builds, {status.error} + Failed to load builds, {status.error.message} ); - } - - return ( - <> - CI/CD Builds + } else { + content = (
+ Status + Branch Message Commit - Status {status.value!.map(build => ( + {/* TODO: make this an indicating blobby thing */} + {build.status} + + + + + = () => { - {build.status} ))}
- + ); + } + + return ( +
+ + CI/CD Builds + + {content} +
); }; diff --git a/frontend/packages/plugins/github-actions/src/plugin.ts b/frontend/packages/plugins/github-actions/src/plugin.ts index 6972d8d128..d95d5b5bdc 100644 --- a/frontend/packages/plugins/github-actions/src/plugin.ts +++ b/frontend/packages/plugins/github-actions/src/plugin.ts @@ -1,6 +1,7 @@ import { createPlugin } from '@backstage/core'; import BuildDetailsPage from './components/BuildDetailsPage'; import BuildListPage from './components/BuildListPage'; +import BuildIcon from '@material-ui/icons/Build'; // export const buildListRoute = createEntityRoute<[]>('/builds') // export const buildDetailsRoute = createEntityRoute<[number]>('/builds/:buildId') @@ -9,7 +10,7 @@ export default createPlugin({ id: 'github-actions', register({ entityPage }) { - entityPage.navItem({ title: 'CI/CD', target: '/builds' }); + entityPage.navItem({ title: 'CI/CD', icon: BuildIcon, target: '/builds' }); entityPage.route('/builds', BuildListPage); entityPage.route('/builds/:buildUri', BuildDetailsPage); }, diff --git a/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.test.tsx b/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.test.tsx index 383bc9a5aa..fc495d3111 100644 --- a/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.test.tsx +++ b/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.test.tsx @@ -1,10 +1,16 @@ import React from 'react'; import { render } from '@testing-library/react'; import HomePage from './HomePage'; +import { ThemeProvider } from '@material-ui/core'; +import { BackstageTheme } from '@backstage/core'; describe('HomePage', () => { it('should render', () => { - const rendered = render(); + const rendered = render( + + + , + ); expect(rendered.baseElement).toBeInTheDocument(); }); }); diff --git a/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.tsx b/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.tsx index 2804cfa2be..0b7c68296b 100644 --- a/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.tsx +++ b/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.tsx @@ -1,10 +1,31 @@ import React, { FC } from 'react'; - -import { EntityLink, InfoCard, SortableTable } from '@backstage/core'; +import { Typography, makeStyles, Theme, Grid } from '@material-ui/core'; +import HomePageTimer from '../HomepageTimer'; +import { + EntityLink, + InfoCard, + SortableTable, + Header, + Page, + theme, +} from '@backstage/core'; import SquadTechHealth from './SquadTechHealth'; -import { Grid, Typography } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + mainContentArea: { + overflowX: 'hidden', + overflowY: 'auto', + }, + pageBody: { + padding: theme.spacing(2), + }, + avatarButton: { + padding: theme.spacing(2), + }, +})); const HomePage: FC<{}> = () => { + const classes = useStyles(); const data = [ { id: 'service-1', system: 'system' }, { id: 'service-2', system: 'system' }, @@ -23,25 +44,34 @@ const HomePage: FC<{}> = () => { ]; return ( - - - - - - - - Welcome to Backstage! -
- - Backstage Backend - - - Backstage LB CI/CD - -
-
-
-
+ +
+
+ +
+
+ + + + + + + + Welcome to Backstage! +
+ + Backstage Backend + + + Backstage LB CI/CD + +
+
+
+
+
+
+
); }; diff --git a/frontend/packages/app/src/components/HomepageTimer/HomepageTimer.tsx b/frontend/packages/plugins/home-page/src/components/HomepageTimer/HomepageTimer.tsx similarity index 100% rename from frontend/packages/app/src/components/HomepageTimer/HomepageTimer.tsx rename to frontend/packages/plugins/home-page/src/components/HomepageTimer/HomepageTimer.tsx diff --git a/frontend/packages/app/src/components/HomepageTimer/index.ts b/frontend/packages/plugins/home-page/src/components/HomepageTimer/index.ts similarity index 100% rename from frontend/packages/app/src/components/HomepageTimer/index.ts rename to frontend/packages/plugins/home-page/src/components/HomepageTimer/index.ts diff --git a/frontend/packages/proto/src/generated/builds/v1/builds_pb.d.ts b/frontend/packages/proto/src/generated/builds/v1/builds_pb.d.ts index ff0c1489cf..6120c5e705 100644 --- a/frontend/packages/proto/src/generated/builds/v1/builds_pb.d.ts +++ b/frontend/packages/proto/src/generated/builds/v1/builds_pb.d.ts @@ -96,6 +96,9 @@ export class Build extends jspb.Message { getMessage(): string; setMessage(value: string): void; + getBranch(): string; + setBranch(value: string): void; + getStatus(): BuildStatus; setStatus(value: BuildStatus): void; @@ -112,6 +115,7 @@ export namespace Build { uri: string, commitId: string, message: string, + branch: string, status: BuildStatus, } } diff --git a/frontend/packages/proto/src/generated/builds/v1/builds_pb.js b/frontend/packages/proto/src/generated/builds/v1/builds_pb.js index 1f00094a1d..6ec016cd99 100644 --- a/frontend/packages/proto/src/generated/builds/v1/builds_pb.js +++ b/frontend/packages/proto/src/generated/builds/v1/builds_pb.js @@ -810,7 +810,8 @@ proto.spotify.backstage.builds.v1.Build.toObject = function(includeInstance, msg uri: jspb.Message.getFieldWithDefault(msg, 1, ""), commitId: jspb.Message.getFieldWithDefault(msg, 2, ""), message: jspb.Message.getFieldWithDefault(msg, 3, ""), - status: jspb.Message.getFieldWithDefault(msg, 4, 0) + branch: jspb.Message.getFieldWithDefault(msg, 4, ""), + status: jspb.Message.getFieldWithDefault(msg, 5, 0) }; if (includeInstance) { @@ -860,6 +861,10 @@ proto.spotify.backstage.builds.v1.Build.deserializeBinaryFromReader = function(m msg.setMessage(value); break; case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setBranch(value); + break; + case 5: var value = /** @type {!proto.spotify.backstage.builds.v1.BuildStatus} */ (reader.readEnum()); msg.setStatus(value); break; @@ -913,10 +918,17 @@ proto.spotify.backstage.builds.v1.Build.serializeBinaryToWriter = function(messa f ); } + f = message.getBranch(); + if (f.length > 0) { + writer.writeString( + 4, + f + ); + } f = message.getStatus(); if (f !== 0.0) { writer.writeEnum( - 4, + 5, f ); } @@ -969,17 +981,32 @@ proto.spotify.backstage.builds.v1.Build.prototype.setMessage = function(value) { /** - * optional BuildStatus status = 4; + * optional string branch = 4; + * @return {string} + */ +proto.spotify.backstage.builds.v1.Build.prototype.getBranch = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** @param {string} value */ +proto.spotify.backstage.builds.v1.Build.prototype.setBranch = function(value) { + jspb.Message.setProto3StringField(this, 4, value); +}; + + +/** + * optional BuildStatus status = 5; * @return {!proto.spotify.backstage.builds.v1.BuildStatus} */ proto.spotify.backstage.builds.v1.Build.prototype.getStatus = function() { - return /** @type {!proto.spotify.backstage.builds.v1.BuildStatus} */ (jspb.Message.getFieldWithDefault(this, 4, 0)); + return /** @type {!proto.spotify.backstage.builds.v1.BuildStatus} */ (jspb.Message.getFieldWithDefault(this, 5, 0)); }; /** @param {!proto.spotify.backstage.builds.v1.BuildStatus} value */ proto.spotify.backstage.builds.v1.Build.prototype.setStatus = function(value) { - jspb.Message.setProto3EnumField(this, 4, value); + jspb.Message.setProto3EnumField(this, 5, value); }; diff --git a/proto/builds/v1/builds.proto b/proto/builds/v1/builds.proto index 7d62de0af7..7da136de7a 100644 --- a/proto/builds/v1/builds.proto +++ b/proto/builds/v1/builds.proto @@ -31,7 +31,8 @@ message Build { string uri = 1; string commit_id = 2; string message = 3; - BuildStatus status = 4; + string branch = 4; + BuildStatus status = 5; } message BuildDetails {