Skip to content

Commit

Permalink
feat(ui): ability to create Stage via UI wizard (#2474)
Browse files Browse the repository at this point in the history
Signed-off-by: Remington Breeze <[email protected]>
  • Loading branch information
rbreeze authored Sep 17, 2024
1 parent 5c88c84 commit eca5560
Show file tree
Hide file tree
Showing 27 changed files with 1,311 additions and 195 deletions.
4 changes: 4 additions & 0 deletions ui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export const App = () => (
<Route path={paths.freight} element={<Project />} />
<Route path={paths.warehouse} element={<Project />} />
<Route path={paths.downloads} element={<Downloads />} />
<Route
path={paths.createStage}
element={<Project tab='pipelines' creatingStage={true} />}
/>
</Route>
</Route>
<Route path={paths.login} element={<Login />} />
Expand Down
1 change: 1 addition & 0 deletions ui/src/config/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const paths = {
stage: '/project/:name/stage/:stageName',
warehouse: '/project/:name/warehouse/:warehouseName/:tab?',
freight: '/project/:name/freight/:freightName',
createStage: '/project/:name/create-stage',

downloads: '/downloads',
login: '/login',
Expand Down
33 changes: 31 additions & 2 deletions ui/src/features/common/form/field-container.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Form, FormItemProps } from 'antd';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Flex, Form, FormItemProps, Tooltip } from 'antd';
import React from 'react';
import {
FieldValues,
Expand All @@ -12,24 +14,51 @@ interface Props<T extends FieldValues> extends UseControllerProps<T> {
label?: string;
formItemOptions?: Omit<FormItemProps, 'label'>;
className?: string;
formItemClassName?: string;
description?: string;
tooltip?: React.ReactNode;
required?: boolean;
}

export const FieldContainer = <T extends FieldValues>({
children,
label,
formItemOptions,
className,
formItemClassName,
description,
tooltip,
required,
...props
}: Props<T>) => {
const controller = useController(props);

return (
<Form layout='vertical' component='div' className={className}>
<Form.Item
{...{ ...formItemOptions, label }}
{...{
...formItemOptions,
label: label && (
<Flex align='center'>
{label}
{required && (
<Tooltip title='Required' placement='right'>
<span className='text-red-500 ml-1'>*</span>
</Tooltip>
)}
{tooltip && (
<Tooltip title={tooltip} placement='top'>
<FontAwesomeIcon icon={faInfoCircle} className='ml-2' />
</Tooltip>
)}
</Flex>
)
}}
className={formItemClassName}
help={controller.fieldState.error?.message}
validateStatus={controller.fieldState.error?.message ? 'error' : ''}
>
{description && <div className='text-xs text-gray-500 mb-2 -mt-1'>{description}</div>}
{children(controller)}
</Form.Item>
</Form>
Expand Down
9 changes: 9 additions & 0 deletions ui/src/features/common/small-label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import classNames from 'classnames';

export const SmallLabel = ({
children,
className
}: {
children: React.ReactNode;
className?: string;
}) => <div className={classNames('text-xs text-gray-500 font-medium', className)}>{children}</div>;
51 changes: 51 additions & 0 deletions ui/src/features/common/stage-tag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Tooltip } from 'antd';

import { Stage } from '@ui/gen/v1alpha1/generated_pb';

import { StagePopover } from '../project/list/project-item/stage-popover';
import { ColorMap } from '../stage/utils';

import { HealthStatusIcon } from './health-status/health-status-icon';
import { PromotionStatusIcon } from './promotion-status/promotion-status-icon';

export const StageTag = ({
stage,
projectName,
stageColorMap
}: {
stage: Stage;
projectName: string;
stageColorMap: ColorMap;
}) => {
return (
<Tooltip
key={stage.metadata?.name}
placement='bottom'
title={
stage?.status?.lastPromotion?.name && <StagePopover project={projectName} stage={stage} />
}
>
<div
className='flex items-center mb-2 text-white rounded py-1 px-2 font-semibold bg-gray-600'
style={{ backgroundColor: stageColorMap[stage.metadata?.name || ''] }}
>
{stage.status?.health && (
<div className='mr-2'>
<HealthStatusIcon health={stage.status?.health} hideColor={true} />
</div>
)}
{!stage?.status?.currentPromotion && stage.status?.lastPromotion && (
<div className='mr-2'>
<PromotionStatusIcon
placement='top'
status={stage.status?.lastPromotion?.status}
color='white'
size='1x'
/>
</div>
)}
{stage.metadata?.name}
</div>
</Tooltip>
);
};
10 changes: 10 additions & 0 deletions ui/src/features/common/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ObjectMeta } from '@ui/gen/k8s.io/apimachinery/pkg/apis/meta/v1/generated_pb';
import { Freight, FreightReference, Stage } from '@ui/gen/v1alpha1/generated_pb';

export const ALIAS_LABEL_KEY = 'kargo.akuity.io/alias';
Expand Down Expand Up @@ -31,3 +32,12 @@ export function currentFreightHasVerification(stage: Stage): boolean {
const collection = stage?.status?.freightHistory[0];
return (collection && (collection.verificationHistory || []).length > 0) || false;
}

export function mapToNames<T extends { metadata?: ObjectMeta }>(objects: T[]) {
return (objects || []).reduce((acc, obj) => {
if (obj?.metadata?.name) {
acc.push(obj.metadata.name);
}
return acc;
}, [] as string[]);
}
40 changes: 6 additions & 34 deletions ui/src/features/project/list/project-item/project-item.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { Tooltip } from 'antd';
import classNames from 'classnames';
import { Link, generatePath } from 'react-router-dom';

import { paths } from '@ui/config/paths';
import { Description } from '@ui/features/common/description';
import { HealthStatusIcon } from '@ui/features/common/health-status/health-status-icon';
import { PromotionStatusIcon } from '@ui/features/common/promotion-status/promotion-status-icon';
import { StageTag } from '@ui/features/common/stage-tag';
import { getColors } from '@ui/features/stage/utils';
import { Project, Stage } from '@ui/gen/v1alpha1/generated_pb';

import * as styles from './project-item.module.less';
import { StagePopover } from './stage-popover';

export const ProjectItem = ({ project, stages }: { project?: Project; stages?: Stage[] }) => {
const stageColorMap = getColors(project?.metadata?.name || '', stages || []);
Expand All @@ -25,37 +22,12 @@ export const ProjectItem = ({ project, stages }: { project?: Project; stages?: S
{(stages || []).length > 0 && (
<div className='flex items-center gap-x-3 gap-y-1 flex-wrap mt-4'>
{stages?.map((stage) => (
<Tooltip
<StageTag
key={stage.metadata?.name}
placement='bottom'
title={
stage?.status?.lastPromotion?.name && (
<StagePopover project={project?.metadata?.name} stage={stage} />
)
}
>
<div
className='flex items-center mb-2 text-white rounded py-1 px-2 font-semibold bg-gray-600'
style={{ backgroundColor: stageColorMap[stage.metadata?.name || ''] }}
>
{stage.status?.health && (
<div className='mr-2'>
<HealthStatusIcon health={stage.status?.health} hideColor={true} />
</div>
)}
{!stage?.status?.currentPromotion && stage.status?.lastPromotion && (
<div className='mr-2'>
<PromotionStatusIcon
placement='top'
status={stage.status?.lastPromotion?.status}
color='white'
size='1x'
/>
</div>
)}
{stage.metadata?.name}
</div>
</Tooltip>
stage={stage}
projectName={project?.metadata?.name || ''}
stageColorMap={stageColorMap}
/>
))}
</div>
)}
Expand Down
83 changes: 0 additions & 83 deletions ui/src/features/project/pipelines/create-stage-modal.tsx

This file was deleted.

28 changes: 19 additions & 9 deletions ui/src/features/project/pipelines/pipelines.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ import { useModal } from '@ui/features/common/modal/use-modal';
const FreightDetails = lazy(() => import('@ui/features/freight/freight-details'));
const FreightTimeline = lazy(() => import('@ui/features/freight-timeline/freight-timeline'));
const StageDetails = lazy(() => import('@ui/features/stage/stage-details'));
const CreateStage = lazy(() => import('@ui/features/stage/create-stage'));
import { SuspenseSpin } from '@ui/features/common/suspense-spin';
import { getCurrentFreight } from '@ui/features/common/utils';
import { getCurrentFreight, mapToNames } from '@ui/features/common/utils';
const FreightTimelineHeader = lazy(
() => import('@ui/features/freight-timeline/freight-timeline-header')
);
Expand All @@ -47,7 +48,6 @@ import { Freight, Project, Stage, Warehouse } from '@ui/gen/v1alpha1/generated_p
import { useDocumentEvent } from '@ui/utils/document';
import { useLocalStorage } from '@ui/utils/use-local-storage';

import CreateStageModal from './create-stage-modal';
import CreateWarehouseModal from './create-warehouse-modal';
import { Images } from './images';
import { RepoNode, RepoNodeDimensions } from './nodes/repo-node';
Expand All @@ -62,7 +62,13 @@ import { Watcher } from './utils/watcher';

const WarehouseDetails = lazy(() => import('./warehouse/warehouse-details'));

export const Pipelines = ({ project }: { project: Project }) => {
export const Pipelines = ({
project,
creatingStage
}: {
project: Project;
creatingStage?: boolean;
}) => {
const { name, stageName, freightName, warehouseName } = useParams();
const { data, isLoading } = useQuery(listStages, { project: name });
const { data: imageData, isLoading: isLoadingImages } = useQuery(listImages, { project: name });
Expand All @@ -77,9 +83,6 @@ export const Pipelines = ({ project }: { project: Project }) => {
project: name
});

const { show: showCreateStage } = useModal(
name ? (p) => <CreateStageModal {...p} project={name} /> : undefined
);
const { show: showCreateWarehouse } = useModal(
name ? (p) => <CreateWarehouseModal {...p} project={name} /> : undefined
);
Expand Down Expand Up @@ -354,16 +357,16 @@ export const Pipelines = ({ project }: { project: Project }) => {
label: (
<>
<FontAwesomeIcon icon={faMasksTheater} size='xs' className='mr-2' />{' '}
Stage
Create Stage
</>
),
onClick: () => showCreateStage()
onClick: () => navigate(generatePath(paths.createStage, { name }))
},
{
key: '2',
label: (
<>
<FontAwesomeIcon icon={faWarehouse} size='xs' className='mr-2' />{' '}
<FontAwesomeIcon icon={faWarehouse} size='xs' className='mr-2' /> Create
Warehouse
</>
),
Expand Down Expand Up @@ -587,6 +590,13 @@ export const Pipelines = ({ project }: { project: Project }) => {
{warehouse && (
<WarehouseDetails warehouse={warehouse} refetchFreight={() => refetchFreightData()} />
)}
{creatingStage && (
<CreateStage
project={name}
warehouses={mapToNames(warehouseData?.warehouses || [])}
stages={mapToNames(data?.stages || [])}
/>
)}
</SuspenseSpin>
</ColorContext.Provider>
</div>
Expand Down
10 changes: 10 additions & 0 deletions ui/src/features/project/pipelines/utils/promo-mechanism-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const promoMechanismExample = `gitRepoUpdates:
- repoURL: https://github.com/akuity/kargo-demo.git
writeBranch: main
kustomize:
images:
- image: public.ecr.aws/nginx/nginx
path: stages/prod
argoCDAppUpdates:
- appName: kargo-demo-prod
appNamespace: argocd`;
Loading

0 comments on commit eca5560

Please sign in to comment.