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 (
);
};
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 {