Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement project details live refresh #574

Merged
merged 2 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions api/service/v1alpha1/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ service KargoService {
rpc CreateStage(CreateStageRequest) returns (CreateStageResponse);
rpc ListStages(ListStagesRequest) returns (ListStagesResponse);
rpc GetStage(GetStageRequest) returns (GetStageResponse);
rpc WatchStage(WatchStageRequest) returns (stream WatchStageResponse);
rpc WatchStages(WatchStagesRequest) returns (stream WatchStagesResponse);
rpc UpdateStage(UpdateStageRequest) returns (UpdateStageResponse);
rpc DeleteStage(DeleteStageRequest) returns (DeleteStageResponse);
rpc PromoteStage(PromoteStageRequest) returns (PromoteStageResponse);
Expand Down Expand Up @@ -116,13 +116,14 @@ message GetStageResponse {
github.com.akuity.kargo.pkg.api.v1alpha1.Stage stage = 1;
}

message WatchStageRequest {
message WatchStagesRequest {
string project = 1;
string name = 2;
}

message WatchStageResponse {
message WatchStagesResponse {
github.com.akuity.kargo.pkg.api.v1alpha1.Stage stage = 1;
string type = 2;
}

message UpdateStageRequest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

type WatchStageV1Alpha1Func func(
context.Context,
*connect.Request[svcv1alpha1.WatchStageRequest],
*connect.ServerStream[svcv1alpha1.WatchStageResponse],
*connect.Request[svcv1alpha1.WatchStagesRequest],
*connect.ServerStream[svcv1alpha1.WatchStagesResponse],
) error

func WatchStageV1Alpha1(
Expand All @@ -32,32 +32,33 @@
stageCli := dynamicCli.Resource(kargov1alpha1.GroupVersion.WithResource("stages"))
return func(
ctx context.Context,
req *connect.Request[svcv1alpha1.WatchStageRequest],
stream *connect.ServerStream[svcv1alpha1.WatchStageResponse],
req *connect.Request[svcv1alpha1.WatchStagesRequest],
stream *connect.ServerStream[svcv1alpha1.WatchStagesResponse],

Check warning on line 36 in internal/api/handler/watch_stages_v1alpha1.go

View check run for this annotation

Codecov / codecov/patch

internal/api/handler/watch_stages_v1alpha1.go#L35-L36

Added lines #L35 - L36 were not covered by tests
) error {
if req.Msg.GetProject() == "" {
return connect.NewError(connect.CodeInvalidArgument, errors.New("project should not be empty"))
}
if req.Msg.GetName() == "" {
return connect.NewError(connect.CodeInvalidArgument, errors.New("name should not be empty"))
}
if err := validateProject(ctx, req.Msg.GetProject()); err != nil {
return err
}

if err := kubeCli.Get(ctx, client.ObjectKey{
Namespace: req.Msg.GetProject(),
Name: req.Msg.GetName(),
}, &kargov1alpha1.Stage{}); err != nil {
if kubeerr.IsNotFound(err) {
return connect.NewError(connect.CodeNotFound, err)
if req.Msg.GetName() != "" {
if err := kubeCli.Get(ctx, client.ObjectKey{
Namespace: req.Msg.GetProject(),
Name: req.Msg.GetName(),
}, &kargov1alpha1.Stage{}); err != nil {
if kubeerr.IsNotFound(err) {
return connect.NewError(connect.CodeNotFound, err)
}
return connect.NewError(connect.CodeInternal, err)

Check warning on line 53 in internal/api/handler/watch_stages_v1alpha1.go

View check run for this annotation

Codecov / codecov/patch

internal/api/handler/watch_stages_v1alpha1.go#L45-L53

Added lines #L45 - L53 were not covered by tests
}
return connect.NewError(connect.CodeInternal, err)
}

w, err := stageCli.Namespace(req.Msg.GetProject()).Watch(ctx, metav1.ListOptions{
FieldSelector: fields.OneTermEqualSelector(metav1.ObjectNameField, req.Msg.GetName()).String(),
})
opts := metav1.ListOptions{}
if req.Msg.GetName() != "" {
opts.FieldSelector = fields.OneTermEqualSelector(metav1.ObjectNameField, req.Msg.GetName()).String()
}
w, err := stageCli.Namespace(req.Msg.GetProject()).Watch(ctx, opts)

Check warning on line 61 in internal/api/handler/watch_stages_v1alpha1.go

View check run for this annotation

Codecov / codecov/patch

internal/api/handler/watch_stages_v1alpha1.go#L57-L61

Added lines #L57 - L61 were not covered by tests
if err != nil {
return errors.Wrap(err, "watch stage")
}
Expand All @@ -78,8 +79,9 @@
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &stage); err != nil {
return errors.Wrap(err, "from unstructured")
}
if err := stream.Send(&svcv1alpha1.WatchStageResponse{
if err := stream.Send(&svcv1alpha1.WatchStagesResponse{

Check warning on line 82 in internal/api/handler/watch_stages_v1alpha1.go

View check run for this annotation

Codecov / codecov/patch

internal/api/handler/watch_stages_v1alpha1.go#L82

Added line #L82 was not covered by tests
Stage: typesv1alpha1.ToStageProto(*stage),
Type: string(e.Type),

Check warning on line 84 in internal/api/handler/watch_stages_v1alpha1.go

View check run for this annotation

Codecov / codecov/patch

internal/api/handler/watch_stages_v1alpha1.go#L84

Added line #L84 was not covered by tests
}); err != nil {
return errors.Wrap(err, "send response")
}
Expand Down
8 changes: 4 additions & 4 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"time"

"connectrpc.com/connect"
grpchealth "connectrpc.com/grpchealth"
"connectrpc.com/grpchealth"
"github.com/pkg/errors"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
Expand Down Expand Up @@ -155,10 +155,10 @@ func (s *server) GetStage(
return handler.GetStageV1Alpha1(s.kubeCli)(ctx, req)
}

func (s *server) WatchStage(
func (s *server) WatchStages(
ctx context.Context,
req *connect.Request[svcv1alpha1.WatchStageRequest],
stream *connect.ServerStream[svcv1alpha1.WatchStageResponse],
req *connect.Request[svcv1alpha1.WatchStagesRequest],
stream *connect.ServerStream[svcv1alpha1.WatchStagesResponse],
) error {
return handler.WatchStageV1Alpha1(s.kubeCli, s.dynamicCli)(ctx, req, stream)
}
Expand Down
689 changes: 349 additions & 340 deletions pkg/api/service/v1alpha1/service.pb.go

Large diffs are not rendered by default.

35 changes: 18 additions & 17 deletions pkg/api/service/v1alpha1/svcv1alpha1connect/service.connect.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 56 additions & 1 deletion ui/src/features/project/project-details/project-details.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,73 @@
import { FlowAnalysisGraph, FlowGraphEdgeData, IGraph, LabelStyle } from '@ant-design/graphs';
import { useQuery } from '@tanstack/react-query';
import { createPromiseClient } from '@bufbuild/connect';
import { createConnectTransport } from '@bufbuild/connect-web';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { Empty } from 'antd';
import React from 'react';
import { generatePath, useNavigate, useParams } from 'react-router-dom';

import { paths } from '@ui/config/paths';
import { LoadingState } from '@ui/features/common';
import { listStages } from '@ui/gen/service/v1alpha1/service-KargoService_connectquery';
import { KargoService } from '@ui/gen/service/v1alpha1/service_connect';
import { Stage } from '@ui/gen/v1alpha1/types_pb';
import { useDocumentEvent } from '@ui/utils/document';

export const ProjectDetails = () => {
const { name } = useParams();
const navigate = useNavigate();
const { data, isLoading } = useQuery(listStages.useQuery({ project: name }));
const graphRef = React.useRef<IGraph | undefined>();
const client = useQueryClient();

const isVisible = useDocumentEvent(
'visibilitychange',
() => document.visibilityState === 'visible'
);

React.useEffect(() => {
if (!data || !isVisible) {
return;
}

const cancel = new AbortController();

const watchStages = async () => {
const transport = createConnectTransport({ baseUrl: '' });
const promiseClient = createPromiseClient(KargoService, transport);
const stream = promiseClient.watchStages(
{ project: 'kargo-demo', name: 'test' },
{ signal: cancel.signal }
);

for await (const e of stream) {
const key = listStages.getQueryKey({ project: name });
const index = data.stages.findIndex(
(item) => item.metadata?.name === e.stage?.metadata?.name
);
let stages = data.stages;
if (e.type === 'DELETED') {
if (index !== -1) {
stages = [...stages.slice(0, index), ...data.stages.slice(index + 1)];
}
} else {
if (index === -1) {
stages = [...stages, e.stage as Stage];
} else {
stages = [
...data.stages.slice(0, index),
e.stage as Stage,
...data.stages.slice(index + 1)
];
}
}
client.setQueryData(key, { stages });
}
};
watchStages();

return () => cancel.abort();
}, [isLoading, isVisible]);

const nodes = React.useMemo(
() =>
Expand Down
12 changes: 6 additions & 6 deletions ui/src/gen/service/v1alpha1/service_connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/* eslint-disable */
// @ts-nocheck

import { AdminLoginRequest, AdminLoginResponse, CreateProjectRequest, CreateProjectResponse, CreatePromotionPolicyRequest, CreatePromotionPolicyResponse, CreateStageRequest, CreateStageResponse, DeleteProjectRequest, DeleteProjectResponse, DeletePromotionPolicyRequest, DeletePromotionPolicyResponse, DeleteStageRequest, DeleteStageResponse, GetPromotionPolicyRequest, GetPromotionPolicyResponse, GetPublicConfigRequest, GetPublicConfigResponse, GetStageRequest, GetStageResponse, GetVersionInfoRequest, GetVersionInfoResponse, ListProjectsRequest, ListProjectsResponse, ListPromotionPoliciesRequest, ListPromotionPoliciesResponse, ListStagesRequest, ListStagesResponse, PromoteStageRequest, PromoteStageResponse, SetAutoPromotionForStageRequest, SetAutoPromotionForStageResponse, UpdatePromotionPolicyRequest, UpdatePromotionPolicyResponse, UpdateStageRequest, UpdateStageResponse, WatchStageRequest, WatchStageResponse } from "./service_pb.js";
import { AdminLoginRequest, AdminLoginResponse, CreateProjectRequest, CreateProjectResponse, CreatePromotionPolicyRequest, CreatePromotionPolicyResponse, CreateStageRequest, CreateStageResponse, DeleteProjectRequest, DeleteProjectResponse, DeletePromotionPolicyRequest, DeletePromotionPolicyResponse, DeleteStageRequest, DeleteStageResponse, GetPromotionPolicyRequest, GetPromotionPolicyResponse, GetPublicConfigRequest, GetPublicConfigResponse, GetStageRequest, GetStageResponse, GetVersionInfoRequest, GetVersionInfoResponse, ListProjectsRequest, ListProjectsResponse, ListPromotionPoliciesRequest, ListPromotionPoliciesResponse, ListStagesRequest, ListStagesResponse, PromoteStageRequest, PromoteStageResponse, SetAutoPromotionForStageRequest, SetAutoPromotionForStageResponse, UpdatePromotionPolicyRequest, UpdatePromotionPolicyResponse, UpdateStageRequest, UpdateStageResponse, WatchStagesRequest, WatchStagesResponse } from "./service_pb.js";
import { MethodKind } from "@bufbuild/protobuf";

/**
Expand Down Expand Up @@ -67,12 +67,12 @@ export const KargoService = {
kind: MethodKind.Unary,
},
/**
* @generated from rpc akuity.io.kargo.service.v1alpha1.KargoService.WatchStage
* @generated from rpc akuity.io.kargo.service.v1alpha1.KargoService.WatchStages
*/
watchStage: {
name: "WatchStage",
I: WatchStageRequest,
O: WatchStageResponse,
watchStages: {
name: "WatchStages",
I: WatchStagesRequest,
O: WatchStagesResponse,
kind: MethodKind.ServerStreaming,
},
/**
Expand Down
Loading
Loading