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(web): packages #981

Merged
merged 8 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 3 additions & 3 deletions web/crux-ui/e2e/utils/config-bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const matchPatchEnvironment = (expected: Record<string, string>) => (message: Pa
)

export const createConfigBundle = async (page: Page, name: string, data: Record<string, string>): Promise<string> => {
await page.goto(TEAM_ROUTES.configBundles.list())
await page.goto(TEAM_ROUTES.configBundle.list())
await page.waitForSelector('h2:text-is("Config bundles")')

await page.locator('button:has-text("Add")').click()
Expand All @@ -20,13 +20,13 @@ export const createConfigBundle = async (page: Page, name: string, data: Record<

const sock = waitSocketRef(page)
await page.locator('text=Save').click()
await page.waitForURL(`${TEAM_ROUTES.configBundles.list()}/**`)
await page.waitForURL(`${TEAM_ROUTES.configBundle.list()}/**`)
await page.waitForSelector(`h4:text-is("View ${name}")`)

const configBundleId = page.url().split('/').pop()

const ws = await sock
const wsRoute = TEAM_ROUTES.configBundles.detailsSocket(configBundleId)
const wsRoute = TEAM_ROUTES.configBundle.detailsSocket(configBundleId)

await page.locator('button:has-text("Edit")').click()

Expand Down
2 changes: 1 addition & 1 deletion web/crux-ui/e2e/with-login/config-bundle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test('Creating a config bundle', async ({ page }) => {
[ENV_KEY]: ENV_VALUE,
})

await page.goto(TEAM_ROUTES.configBundles.details(configBundleId))
await page.goto(TEAM_ROUTES.configBundle.details(configBundleId))

const keyInput = page.locator('input[placeholder="Key"]').first()
await expect(keyInput).toBeDisabled()
Expand Down
4 changes: 3 additions & 1 deletion web/crux-ui/i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
"/[teamSlug]/storages": ["storages"],
"/[teamSlug]/storages/[storageId]": ["storages"],
"/[teamSlug]/config-bundles": ["config-bundles"],
"/[teamSlug]/config-bundles/[configBundleId]": ["config-bundles"],
"/[teamSlug]/packages": ["packages"],
"/[teamSlug]/packages/[packageId]": ["packages"],
"/[teamSlug]/packages/[packageId]/environments/[environmentId]": ["packages", "deployments"],
"/[teamSlug]/pipelines": ["pipelines"],
"/[teamSlug]/pipelines/[pipelineId]": ["pipelines"]
}
Expand Down
1 change: 1 addition & 0 deletions web/crux-ui/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"pipelines": "Pipelines",
"storages": "Storages",
"configBundles": "Config bundles",
m8vago marked this conversation as resolved.
Show resolved Hide resolved
"packages": "Packages",

"role": {
"owner": "Owner",
Expand Down
16 changes: 16 additions & 0 deletions web/crux-ui/locales/en/packages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"new": "New package",
"packageName": "Package - {{name}}",
"newEnvironment": "New environment",
"tips": "Packages are a composition of related project versions and nodes. By creating a package you can track and update your applications across various nodes.",
"noItems": "You haven't created a package yet. Make one by clicking 'Add' on the top right.",
"environments": "Environments",
"addEnvironment": "Add environment",
"versionChains": "Version chains",
"addVersionChains": "Add version chains",
"earliest": "Earliest",
"latest": "Latest",
"noVersionChains": "You haven't selected any version chain yet.",
"createDeployment": "Create deployment",
"deployed": "Deployed"
}
8 changes: 1 addition & 7 deletions web/crux-ui/public/chevron_down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions web/crux-ui/public/package.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions web/crux-ui/quality-assurance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export const QA_MODAL_LABEL_NODE_AUDIT_DETAILS = 'nodeAuditDetails'
export const QA_MODAL_LABEL_DEPLOYMENT_NOTE = 'deploymentNote'
export const QA_MODAL_LABEL_IMAGE_TAGS = 'image-tags'
export const QA_MODAL_LABEL_AUDIT_LOG_DETAILS = 'auditLogDetails'
export const QA_MODAL_LABEL_CREATE_PACKAGE_DEPLOYMENT = 'createPackageDeployment'

export type QualityAssurance = {
disabled: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const AddConfigBundleCard = (props: AddConfigBundleCardProps) => {
...values,
}

const res = await sendForm('POST', routes.configBundles.api.list(), body)
const res = await sendForm('POST', routes.configBundle.api.list(), body)

if (res.ok) {
let result: ConfigBundle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const ConfigBundleCard = (props: ConfigBundleCardProps) => {

const { t } = useTranslation('config-bundles')
const routes = useTeamRoutes()
const titleHref = routes.configBundles.details(configBundle.id)
const titleHref = routes.configBundle.details(configBundle.id)

return (
<DyoCard className={clsx(className ?? 'p-6', 'flex flex-col')}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const useConfigBundleDetailsState = (
const [fieldErrors, setFieldErrors] = useState<ValidationError[]>([])
const [topBarContent, setTopBarContent] = useState<React.ReactNode>(null)

const sock = useWebSocket(routes.configBundles.detailsSocket(configBundle.id), {
const sock = useWebSocket(routes.configBundle.detailsSocket(configBundle.id), {
onOpen: () => setSaveState('connected'),
onClose: () => setSaveState('disconnected'),
onSend: message => {
Expand All @@ -89,12 +89,12 @@ export const useConfigBundleDetailsState = (
})

const onDelete = async (): Promise<void> => {
const res = await fetch(routes.configBundles.api.details(configBundle.id), {
const res = await fetch(routes.configBundle.api.details(configBundle.id), {
method: 'DELETE',
})

if (res.ok) {
await router.replace(routes.configBundles.list())
await router.replace(routes.configBundle.list())
} else if (res.status === 412) {
toastWarning(t('inUse'))
} else {
Expand Down
60 changes: 25 additions & 35 deletions web/crux-ui/src/components/deployments/add-deployment-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { createFullDeploymentSchema } from '@app/validations'
import useTranslation from 'next-translate/useTranslation'
import { useEffect } from 'react'
import useSWR from 'swr'
import SelectNodeChips from '../nodes/select-node-chips'
import SelectProjectChips from '../projects/select-project-chips'

interface AddDeploymentCardProps {
Expand All @@ -40,8 +41,6 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
const { t } = useTranslation('deployments')
const routes = useTeamRoutes()

const { data: nodes, error: fetchNodesError } = useSWR<DyoNode[]>(routes.node.api.list(), fetcher)

const handleApiError = apiErrorHandler((stringId: string, status: number, dto: DyoApiError) => {
if (deploymentHasError(dto)) {
onAdd(dto.value)
Expand All @@ -60,7 +59,7 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {

const formik = useDyoFormik({
initialValues: {
nodeId: null as string,
node: null as DyoNode,
note: '',
prefix: '',
protected: false,
Expand All @@ -71,7 +70,10 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
t,
onSubmit: async (values, { setFieldError }) => {
const body: CreateDeployment = {
...values,
nodeId: values.node?.id,
prefix: values.prefix,
protected: values.protected,
versionId: values.versionId,
}

const res = await sendForm('POST', routes.deployment.api.list(), body)
Expand All @@ -93,14 +95,7 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
fetcher,
)

const { setFieldValue: formikSetFieldValue, setFieldError: formikSetFieldError, values: formikValues } = formik

useEffect(() => {
if (nodes?.length === 1 && !formikValues.nodeId) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
formikSetFieldValue('nodeId', nodes[0].id)
}
}, [nodes, formikValues.nodeId, formikSetFieldValue])
const { setFieldValue: formikSetFieldValue, setFieldError: formikSetFieldError } = formik

useEffect(() => {
if (versions?.length === 1) {
Expand All @@ -122,6 +117,13 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
}
}

const onNodesFetched = (nodes: DyoNode[] | null) => {
if (nodes?.length === 1) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
formikSetFieldValue('project', nodes[0])
}
}

const currentProject = formik.values.project

return (
Expand All @@ -142,28 +144,16 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {

<div className="flex flex-col">
<DyoLabel className="mt-8 mb-2.5">{t('common:nodes')}</DyoLabel>
{fetchNodesError ? (
<DyoLabel>
{t('errors:fetchFailed', {
type: t('common:nodes'),
})}
</DyoLabel>
) : !nodes ? (
<DyoLabel>{t('common:loading')}</DyoLabel>
) : nodes.length === 0 ? (
<DyoMessage message={t('common:noNameFound', { name: t('common:nodes') })} messageType="error" />
) : (
<>
<DyoChips
name="nodes"
choices={nodes ?? []}
converter={(it: DyoNode) => it.name}
selection={nodes.find(it => it.id === formik.values.nodeId)}
onSelectionChange={it => formik.setFieldValue('nodeId', it.id)}
/>
{formik.errors.nodeId && <DyoMessage message={formik.errors.nodeId} messageType="error" />}
</>
)}

<SelectNodeChips
name="nodes"
onSelectionChange={async it => {
await formik.setFieldValue('node', it)
}}
onNodesFetched={onNodesFetched}
selection={formik.values.node}
errorMessage={formik.errors.node as string}
/>

<DyoLabel className="mt-8 mb-2.5">{t('common:projects')}</DyoLabel>

Expand All @@ -187,7 +177,7 @@ const AddDeploymentCard = (props: AddDeploymentCardProps) => {
type: t('common:versions'),
})}
</DyoLabel>
) : !versions && formik.values.project ? (
) : !versions && currentProject ? (
<DyoLabel>{t('common:loading')}</DyoLabel>
) : versions.length === 0 ? (
<DyoMessage message={t('noVersions')} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ const DeploymentDetailsSection = (props: DeploymentDetailsSectionProps) => {

const editorState = useItemEditorState(editor, sock, ITEM_ID)

const { data: configBundleOptions } = useSWR<ConfigBundleOption[]>(teamRoutes.configBundles.api.options(), fetcher)
const { data: configBundleOptions } = useSWR<ConfigBundleOption[]>(teamRoutes.configBundle.api.options(), fetcher)

const configBundlesHref =
deployment.configBundleIds?.length === 1
? teamRoutes.configBundles.details(deployment.configBundleIds[0])
: teamRoutes.configBundles.list()
? teamRoutes.configBundle.details(deployment.configBundleIds[0])
: teamRoutes.configBundle.list()

return (
<DyoCard className={clsx('flex flex-col', className ?? 'p-6')}>
Expand Down
7 changes: 6 additions & 1 deletion web/crux-ui/src/components/main/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ export const sidebarSectionsOf = (routes: TeamRoutes): MenuSection[] => [
{
icon: '/config_bundle.svg',
text: 'configBundles',
link: routes.configBundles.list(),
link: routes.configBundle.list(),
},
{
icon: '/package.svg',
text: 'packages',
link: routes.package.list(),
},
],
},
Expand Down
3 changes: 2 additions & 1 deletion web/crux-ui/src/components/nodes/node-audit-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ const NodeAuditList = (props: NodeAuditListProps) => {
/>
</DyoTable>
</DyoCard>
{!showInfo ? null : (

{showInfo && (
<DyoModal
className="w-1/2 h-1/2"
titleClassName="pl-4 font-medium text-xl text-bright mb-3"
Expand Down
3 changes: 2 additions & 1 deletion web/crux-ui/src/components/nodes/node-deployment-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ const NodeDeploymentList = (props: NodeDeploymentListProps) => {
{t('noItems')}
</DyoHeading>
)}
{!showInfo ? null : (

{showInfo && (
<DyoModal
className="w-1/2 h-1/2"
titleClassName="pl-4 font-medium text-xl text-bright mb-3"
Expand Down
59 changes: 59 additions & 0 deletions web/crux-ui/src/components/nodes/select-node-chips.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import DyoChips from '@app/elements/dyo-chips'
import { DyoLabel } from '@app/elements/dyo-label'
import DyoMessage from '@app/elements/dyo-message'
import useTeamRoutes from '@app/hooks/use-team-routes'
import { DyoNode } from '@app/models'
import { fetcher } from '@app/utils'
import useTranslation from 'next-translate/useTranslation'
import { useEffect } from 'react'
import useSWR from 'swr'

type SelectNodeChipsProps = {
className?: string
name: string
selection: DyoNode | null
onSelectionChange: (node: DyoNode) => Promise<void>
errorMessage?: string | null
onNodesFetched?: (nodes: DyoNode[] | null) => void
}

const SelectNodeChips = (props: SelectNodeChipsProps) => {
const { className, name, selection, onSelectionChange, errorMessage, onNodesFetched } = props

const { t } = useTranslation('common')
const routes = useTeamRoutes()

const { data: nodes, error: fetchError } = useSWR<DyoNode[]>(routes.node.api.list(), fetcher)

useEffect(() => {
onNodesFetched?.call(null, nodes)

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes])

return fetchError ? (
<DyoLabel>
{t('errors:fetchFailed', {
type: t('common:nodes'),
})}
</DyoLabel>
) : !nodes ? (
<DyoLabel>{t('common:loading')}</DyoLabel>
) : nodes.length === 0 ? (
<DyoMessage message={t('common:noNameFound', { name: t('common:node') })} messageType="error" />
) : (
<>
<DyoChips
className={className}
name={name}
choices={nodes ?? []}
converter={(it: DyoNode) => it.name}
selection={selection}
onSelectionChange={onSelectionChange}
/>
{errorMessage && <DyoMessage message={errorMessage} messageType="error" />}
</>
)
}

export default SelectNodeChips
Loading
Loading