dgtown hace 2 años
padre
commit
0a80097e58

+ 4 - 0
api/client/porter_app.go

@@ -298,6 +298,7 @@ type UpdateAppInput struct {
 	Base64PorterYAML   string
 	IsEnvOverride      bool
 	WithPredeploy      bool
+	Base64Description  string
 }
 
 // UpdateApp updates a porter app
@@ -318,6 +319,7 @@ func (c *Client) UpdateApp(
 		Base64PorterYAML:   inp.Base64PorterYAML,
 		IsEnvOverride:      inp.IsEnvOverride,
 		WithPredeploy:      inp.WithPredeploy,
+		Base64Description:  inp.Base64Description,
 	}
 
 	err := c.postRequest(
@@ -719,10 +721,12 @@ func (c *Client) UpdateImage(
 	ctx context.Context,
 	projectID, clusterID uint,
 	appName, deploymentTargetName, tag string,
+	base64Description string,
 ) (*porter_app.UpdateImageResponse, error) {
 	req := &porter_app.UpdateImageRequest{
 		Tag:                  tag,
 		DeploymentTargetName: deploymentTargetName,
+		Base64Description:    base64Description,
 	}
 
 	resp := &porter_app.UpdateImageResponse{}

+ 3 - 0
api/server/handlers/porter_app/update_app.go

@@ -71,6 +71,8 @@ type UpdateAppRequest struct {
 	IsEnvOverride bool `json:"is_env_override"`
 	// WithPredeploy is a flag to indicate whether to run the predeploy job
 	WithPredeploy bool `json:"with_predeploy"`
+	// Description is a user-generated description for the update
+	Base64Description string `json:"base64_description"`
 }
 
 // UpdateAppResponse is the response object for the POST /apps/update endpoint
@@ -263,6 +265,7 @@ func (c *UpdateAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		Addons:              addons,
 		AddonOverrides:      addonOverrides,
 		IsPredeployEligible: request.WithPredeploy,
+		B64Description:      request.Base64Description,
 	})
 
 	ccpResp, err := c.Config().ClusterControlPlaneClient.UpdateApp(ctx, updateReq)

+ 2 - 0
api/server/handlers/porter_app/update_image.go

@@ -40,6 +40,7 @@ type UpdateImageRequest struct {
 	DeploymentTargetName string `json:"deployment_target_name"`
 	Repository           string `json:"repository"`
 	Tag                  string `json:"tag"`
+	Base64Description    string `json:"base64_description"`
 }
 
 // UpdateImageResponse is the response object for the /apps/{porter_app_name}/update-image endpoint
@@ -111,6 +112,7 @@ func (c *UpdateImageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			Id:   request.DeploymentTargetID,
 			Name: deploymentTargetName,
 		},
+		B64Description: request.Base64Description,
 	})
 	ccpResp, err := c.Config().ClusterControlPlaneClient.UpdateAppImage(ctx, updateImageReq)
 	if err != nil {

+ 10 - 0
cli/cmd/commands/app.go

@@ -46,6 +46,7 @@ var (
 	appWait              bool
 	deploymentTargetName string
 	jobName              string
+	description          string
 )
 
 const (
@@ -126,6 +127,14 @@ func registerCommand_App(cliConf config.CLIConfig) *cobra.Command {
 		"",
 		"the specified tag to use, default is \"latest\"",
 	)
+
+	appUpdateTagCmd.PersistentFlags().StringVarP(
+		&description,
+		"description",
+		"d",
+		"",
+		"optional description for this update",
+	)
 	appCmd.AddCommand(appUpdateTagCmd)
 
 	// appRollback represents the "porter app rollback" subcommand
@@ -1258,6 +1267,7 @@ func appUpdateTag(ctx context.Context, user *types.GetAuthenticatedUserResponse,
 			AppName:                     args[0],
 			DeploymentTargetName:        deploymentTargetName,
 			Tag:                         appTag,
+			Description:                 description,
 			Client:                      client,
 			WaitForSuccessfulDeployment: appWait,
 		})

+ 2 - 0
cli/cmd/commands/apply.go

@@ -113,6 +113,7 @@ applying a configuration:
 	applyCmd.PersistentFlags().BoolVarP(&previewApply, "preview", "p", false, "apply as preview environment based on current git branch")
 	applyCmd.PersistentFlags().BoolVar(&pullImageBeforeBuild, "pull-before-build", false, "attempt to pull image from registry before building")
 	applyCmd.PersistentFlags().StringVar(&imageTagOverride, "tag", "", "set the image tag used for the application (overrides field in yaml)")
+	applyCmd.PersistentFlags().StringVarP(&description, "description", "d", "", "an optional description for this update")
 	applyCmd.PersistentFlags().BoolVar(&predeploy, "predeploy", false, "run predeploy job before deploying the application")
 	applyCmd.PersistentFlags().BoolVarP(
 		&appWait,
@@ -159,6 +160,7 @@ func apply(ctx context.Context, _ *types.GetAuthenticatedUserResponse, client ap
 			WaitForSuccessfulDeployment: appWait,
 			PullImageBeforeBuild:        pullImageBeforeBuild,
 			WithPredeploy:               predeploy,
+			Description:                 description,
 		}
 		err := v2.Apply(ctx, inp)
 		if err != nil {

+ 8 - 0
cli/cmd/v2/apply.go

@@ -44,6 +44,8 @@ type ApplyInput struct {
 	PullImageBeforeBuild bool
 	// WithPredeploy is true when Apply should run the predeploy step
 	WithPredeploy bool
+	// Description is the description for the image update
+	Description string
 }
 
 // Apply implements the functionality of the `porter apply` command for validate apply v2 projects
@@ -111,6 +113,11 @@ func Apply(ctx context.Context, inp ApplyInput) error {
 		return fmt.Errorf("error getting git source from env: %w", err)
 	}
 
+	var base64Description string
+	if inp.Description != "" {
+		base64Description = base64.StdEncoding.EncodeToString([]byte(inp.Description))
+	}
+
 	updateInput := api.UpdateAppInput{
 		ProjectID:          cliConf.Project,
 		ClusterID:          cliConf.Cluster,
@@ -121,6 +128,7 @@ func Apply(ctx context.Context, inp ApplyInput) error {
 		CommitSHA:          commitSHA,
 		Base64PorterYAML:   b64YAML,
 		WithPredeploy:      inp.WithPredeploy,
+		Base64Description:  base64Description,
 	}
 
 	updateResp, err := client.UpdateApp(ctx, updateInput)

+ 8 - 1
cli/cmd/v2/update_image.go

@@ -2,6 +2,7 @@ package v2
 
 import (
 	"context"
+	"encoding/base64"
 	"fmt"
 
 	"github.com/fatih/color"
@@ -16,6 +17,7 @@ type UpdateImageInput struct {
 	AppName                     string
 	DeploymentTargetName        string
 	Tag                         string
+	Description                 string
 	Client                      api.Client
 	WaitForSuccessfulDeployment bool
 }
@@ -27,7 +29,12 @@ func UpdateImage(ctx context.Context, input UpdateImageInput) error {
 		tag = "latest"
 	}
 
-	resp, err := input.Client.UpdateImage(ctx, input.ProjectID, input.ClusterID, input.AppName, input.DeploymentTargetName, tag)
+	var base64Description string
+	if input.Description != "" {
+		base64Description = base64.StdEncoding.EncodeToString([]byte(input.Description))
+	}
+
+	resp, err := input.Client.UpdateImage(ctx, input.ProjectID, input.ClusterID, input.AppName, input.DeploymentTargetName, tag, base64Description)
 	if err != nil {
 		return fmt.Errorf("unable to update image: %w", err)
 	}

+ 3 - 0
dashboard/src/assets/message_text.svg

@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M6.8 7.1999H15.2M6.8 11.9999H11.6M11.287 16.5912L6.27826 21.5999V16.5912H4.4C3.07452 16.5912 2 15.5167 2 14.1912V4.7999C2 3.47442 3.07452 2.3999 4.4 2.3999H18.8C20.1255 2.3999 21.2 3.47442 21.2 4.7999V14.1912C21.2 15.5167 20.1255 16.5912 18.8 16.5912H11.287Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 18 - 1
dashboard/src/lib/hooks/useRevisionList.ts

@@ -22,11 +22,15 @@ export function useRevisionList({
   revisionList: AppRevision[];
   revisionIdToNumber: Record<string, number>;
   numberToRevisionId: Record<number, string>;
+  revisionIdToDescription: Record<string, string>;
 } {
   const [revisionList, setRevisionList] = useState<AppRevision[]>([]);
   const [revisionIdToNumber, setRevisionIdToNumber] = useState<
     Record<string, number>
   >({});
+  const [revisionIdToDescription, setRevisionIdToDescription] = useState<
+    Record<string, string>
+  >({});
   const [numberToRevisionId, setNumberToRevisionId] = useState<
     Record<number, string>
   >({});
@@ -74,11 +78,24 @@ export function useRevisionList({
       setRevisionIdToNumber(
         Object.fromEntries(revisionList.map((r) => [r.id, r.revision_number]))
       );
+      setRevisionIdToDescription(
+        Object.fromEntries(
+          revisionList.map((r) => [
+            r.id,
+            r.b64_description ? atob(r.b64_description) : "",
+          ])
+        )
+      );
       setNumberToRevisionId(
         Object.fromEntries(revisionList.map((r) => [r.revision_number, r.id]))
       );
     }
   }, [data]);
 
-  return { revisionList, revisionIdToNumber, numberToRevisionId };
+  return {
+    revisionList,
+    revisionIdToNumber,
+    numberToRevisionId,
+    revisionIdToDescription,
+  };
 }

+ 1 - 0
dashboard/src/lib/revisions/types.ts

@@ -37,6 +37,7 @@ export const appRevisionValidator = z.object({
   created_at: z.string(),
   updated_at: z.string(),
   app_instance_id: z.string(),
+  b64_description: z.string().optional(),
 });
 
 export type AppRevision = z.infer<typeof appRevisionValidator>;

+ 37 - 6
dashboard/src/main/home/app-dashboard/app-view/tabs/activity-feed/events/cards/DeployEventCard.tsx

@@ -18,6 +18,7 @@ import alert from "assets/alert-warning.svg";
 import deploy from "assets/deploy.png";
 import view_changes from "assets/edit-contained.svg";
 import revert from "assets/fast-backward.svg";
+import message_text from "assets/message_text.svg";
 import pull_request_icon from "assets/pull_request_icon.svg";
 import run_for from "assets/run_for.png";
 import tag_icon from "assets/tag.png";
@@ -82,12 +83,13 @@ const DeployEventCard: React.FC<Props> = ({
         MAX_DISPLAYED_SERVICE_STATUSES
   );
 
-  const { revisionIdToNumber, numberToRevisionId } = useRevisionList({
-    appName,
-    deploymentTargetId,
-    projectId,
-    clusterId,
-  });
+  const { revisionIdToNumber, numberToRevisionId, revisionIdToDescription } =
+    useRevisionList({
+      appName,
+      deploymentTargetId,
+      projectId,
+      clusterId,
+    });
   const {
     latestRevision,
     porterApp,
@@ -355,6 +357,22 @@ const DeployEventCard: React.FC<Props> = ({
           <Text color="helper">{getDuration(event)}</Text>
         </Container>
       </Container>
+      {revisionIdToDescription[event.metadata.app_revision_id] && (
+        <>
+          <Spacer y={0.5} />
+          <Container row spaced>
+            <Container row>
+              <Spacer x={1} inline />
+              <DescriptionContainer>
+                <Icon height="16px" src={message_text} />
+                <Spacer x={0.75} inline />
+                {revisionIdToDescription[event.metadata.app_revision_id]}
+              </DescriptionContainer>
+              <Spacer x={1} inline />
+            </Container>
+          </Container>
+        </>
+      )}
       <Spacer y={0.5} />
       <Container row spaced>
         <Container row>
@@ -453,3 +471,16 @@ const StatusTextContainer = styled.div`
   align-items: center;
   flex-direction: row;
 `;
+
+const DescriptionContainer = styled.div`
+  background: #44444422;
+  border-radius: 5px;
+  padding: 8px;
+  display: flex;
+  width: 100%;
+  border-radius: 5px;
+  border: 1px solid ${({ theme }) => theme.border};
+  align-items: center;
+  font-family: monospace;
+  font-size: 12px;
+`;

+ 3 - 0
internal/models/app_revision.go

@@ -84,4 +84,7 @@ type AppRevision struct {
 
 	// AppInstanceID is the ID of the AppInstance that the revision belongs to. This will be null while the app instance table is being seeded (tracking: POR-1991)
 	AppInstanceID uuid.UUID `json:"app_instance_id" gorm:"type:uuid;default:00000000-0000-0000-0000-000000000000"`
+
+	// B64Description is a user-provided description of the revision
+	Base64Description string `json:"base64_description" gorm:"default:''"`
 }

+ 3 - 0
internal/porter_app/revisions.go

@@ -39,6 +39,8 @@ type Revision struct {
 	Env environment_groups.EnvironmentGroup `json:"env,omitempty"`
 	// AppInstanceID is the id of the app instance the revision is associated with
 	AppInstanceID uuid.UUID `json:"app_instance_id"`
+	// Description is the description of the revision
+	B64Description string `json:"b64_description"`
 }
 
 // RevisionProgress describes the progress of a revision in its lifecycle
@@ -160,6 +162,7 @@ func EncodedRevisionFromProto(ctx context.Context, appRevision *porterv1.AppRevi
 		UpdatedAt:        appRevision.UpdatedAt.AsTime(),
 		DeploymentTarget: DeploymentTarget{ID: appRevision.DeploymentTargetId},
 		AppInstanceID:    appInstanceId,
+		B64Description:   appRevision.B64Description,
 	}
 
 	return revision, nil