feat(new-nav): add creation flows for Jobs#2605
feat(new-nav): add creation flows for Jobs#2605rmnbrd wants to merge 16 commits intonew-navigationfrom
Conversation
170bab6 to
6f109e1
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## new-navigation #2605 +/- ##
=================================================
Coverage ? 45.15%
=================================================
Files ? 732
Lines ? 17674
Branches ? 5246
=================================================
Hits ? 7980
Misses ? 8257
Partials ? 1437
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds new-nav (TanStack Router) creation flows for Lifecycle Jobs and Cron Jobs, migrating the job creation UI into a new @qovery/domains/service-job/feature domain package.
Changes:
- Introduces a new
service-jobfeature library implementing the multi-step job creation flow (general/dockerfile/configure/resources/variables/summary). - Wires new
/service/create/{lifecycle-job|cron-job}routes into console-v5 and updates “Service New” cards to point to the new flow URLs. - Removes legacy job creation feature/pages and related tests/snapshots from
libs/pages/services.
Reviewed changes
Copilot reviewed 60 out of 67 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.base.json | Adds TS path alias for the new @qovery/domains/service-job/feature library. |
| libs/shared/routes/src/lib/sub-router/job.router.ts | Updates lifecycle template creation URL builder (now querystring-based). |
| libs/pages/services/src/lib/feature/page-job-create-feature/template-form-sync.tsx | Removes legacy template sync component for old job creation flow. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-variable-feature/step-variable-feature.spec.tsx | Removes legacy step-variable feature test. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-summary-feature/step-summary-feature.tsx | Removes legacy summary step implementation. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-summary-feature/step-summary-feature.spec.tsx | Removes legacy summary step test. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-resources-feature/step-resources-feature.tsx | Removes legacy resources step implementation. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-resources-feature/step-resources-feature.spec.tsx | Removes legacy resources step test. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-general-feature/step-general-feature.tsx | Removes legacy general step implementation. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-general-feature/step-general-feature.spec.tsx | Removes legacy general step test. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-dockerfile-feature/step-dockerfile-feature.spec.tsx | Removes legacy dockerfile step snapshot test. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-dockerfile-feature/snapshots/step-dockerfile-feature.spec.tsx.snap | Removes legacy dockerfile snapshot. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-configure-feature/step-configure-feature.tsx | Removes legacy configure step implementation. |
| libs/pages/services/src/lib/feature/page-job-create-feature/step-configure-feature/step-configure-feature.spec.tsx | Removes legacy configure step test. |
| libs/pages/services/src/lib/feature/page-job-create-feature/page-job-create-feature.tsx | Removes legacy job creation feature container/context. |
| libs/pages/services/src/lib/feature/page-job-create-feature/page-job-create-feature.spec.tsx | Removes legacy page-job-create feature test. |
| libs/domains/services/feature/src/lib/service-new/service-new.tsx | Updates “new service” links to use /service/create/... flow paths for jobs. |
| libs/domains/services/feature/src/lib/job-general-settings/job-general-settings.tsx | Minor formatting change (whitespace). |
| libs/domains/services/feature/src/lib/dockerfile-settings/dockerfile-settings.tsx | Updates text colors and fixes ExternalLink to include href. |
| libs/domains/services/feature/src/lib/dockerfile-settings/snapshots/dockerfile-settings.spec.tsx.snap | Updates snapshots for DockerfileSettings UI changes. |
| libs/domains/service-settings/feature/src/lib/job-configuration/job-configuration.tsx | Switches JobConfigurationForm import to the new service-job feature package. |
| libs/domains/service-job/feature/tsconfig.spec.json | Adds TS config for tests in the new library. |
| libs/domains/service-job/feature/tsconfig.lib.json | Adds TS config for building the new library. |
| libs/domains/service-job/feature/tsconfig.json | Adds TS project references/config for the new library. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-variables/step-variables.tsx | Implements variables step using TanStack router navigation. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-summary/step-summary.tsx | Adds new summary step (prepare request, create service, import variables, optional deploy). |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-resources/step-resources.tsx | Adds resources step using shared ApplicationSettingsResources. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-introduction/util-localstorage-step.ts | Adds localStorage helpers for lifecycle introduction step. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-introduction/step-introduction.tsx | Adds lifecycle-job introduction step UI using TanStack router. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-introduction/step-introduction.spec.tsx | Adds/updates unit test for the introduction step. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-introduction/images/trigger.svg | Adds new SVG asset for the introduction step. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-introduction/images/build.svg | Adds new SVG asset for the introduction step. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-general/step-general.tsx | Adds general step UI for job creation (source/build/deploy/labels). |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-dockerfile/step-dockerfile.tsx | Adds dockerfile step for lifecycle jobs with dockerfile validation. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-configure/step-configure.tsx | Adds configure/triggers step and integrates JobConfigurationForm. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/step-configure/step-configure.spec.tsx | Adds unit tests around configure step validation behavior. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/job-creation-flow.tsx | Adds creation-flow context/provider and funnel wrapper for jobs. |
| libs/domains/service-job/feature/src/lib/job-creation-flow/job-creation-flow.spec.tsx | Adds a spec file (currently not aligned with job flow). |
| libs/domains/service-job/feature/src/lib/job-creation-flow/job-create-utils/job-create-utils.ts | Adds helper to resolve template/option metadata from service templates. |
| libs/domains/service-job/feature/src/lib/job-configuration-form/job-configuration-form.tsx | Adds shared JobConfigurationForm (cron + lifecycle events + execution behavior). |
| libs/domains/service-job/feature/src/lib/job-configuration-form/job-configuration-form.spec.tsx | Adds tests for JobConfigurationForm (cron + lifecycle variants). |
| libs/domains/service-job/feature/src/index.ts | Exports new service-job feature modules/components. |
| libs/domains/service-job/feature/project.json | Adds Nx project configuration for the new library. |
| libs/domains/service-job/feature/jest.config.ts | Adds Jest config for the new library. |
| libs/domains/service-job/feature/README.md | Adds generated README for the new library. |
| libs/domains/service-job/feature/.eslintrc.json | Adds ESLint config for the new library. |
| libs/domains/service-job/feature/.babelrc | Adds Babel config for the new library. |
| apps/console-v5/src/routes/_authenticated/organization/route.tsx | Adds lifecycle-job/cron-job creation routes to bypass layout list. |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/variables.tsx | Adds lifecycle-job variables route (renders StepVariables). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/summary.tsx | Adds lifecycle-job summary route (renders StepSummary). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/route.tsx | Adds lifecycle-job route wrapper (JobCreationFlow provider + Outlet). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/resources.tsx | Adds lifecycle-job resources route (renders StepResources). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/introduction.tsx | Adds lifecycle-job introduction route (renders StepIntroduction). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/index.tsx | Adds lifecycle-job index redirect (introduction vs general). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/general.tsx | Adds lifecycle-job general route (renders StepGeneral). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/dockerfile.tsx | Adds lifecycle-job dockerfile route (renders StepDockerfile). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/configure.tsx | Adds lifecycle-job configure route (renders StepConfigure). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/variables.tsx | Adds cron-job variables route (renders StepVariables). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/summary.tsx | Adds cron-job summary route (renders StepSummary). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/route.tsx | Adds cron-job route wrapper (JobCreationFlow provider + Outlet). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/resources.tsx | Adds cron-job resources route (renders StepResources). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/index.tsx | Adds cron-job index redirect to general step. |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/general.tsx | Adds cron-job general route (renders StepGeneral). |
| apps/console-v5/src/routes/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/cron-job/configure.tsx | Adds cron-job configure route (renders StepConfigure). |
Comments suppressed due to low confidence (1)
libs/domains/service-job/feature/src/lib/job-creation-flow/step-dockerfile/step-dockerfile.tsx:38
pathCreateis hard-coded to the lifecycle-job configure URL, ignoringjobURL. This tightly couples the component to lifecycle jobs and will break if the base route changes (and makes reuse in other flows impossible). Prefer navigating tojobURL + '/configure'(or a route pattern with params) rather than constructing a fixed absolute string here.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function StepConfigureContent(props: StepConfigureProps) { | ||
| const { formState, watch } = useFormContext<JobConfigureData>() | ||
| const [isValid, setIsValid] = useState(true) | ||
|
|
||
| watch((data) => { | ||
| if (props.jobType === ServiceTypeEnum.LIFECYCLE_JOB) { | ||
| setIsValid(Boolean(data.on_start?.enabled || data.on_stop?.enabled || data.on_delete?.enabled)) | ||
| } | ||
| }) |
There was a problem hiding this comment.
watch((data) => { ... setIsValid(...) }) is being called on every render, creating a new subscription each time (and triggering state updates during render). This can cause memory leaks and render loops. Move the subscription into a useEffect that returns the unsubscribe function, or use useWatch/watch values and derive isValid without subscribing in render.
| displayName: 'domains-service-helm-feature', | ||
| preset: '../../../../jest.preset.js', | ||
| transform: { | ||
| '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', | ||
| '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], | ||
| }, | ||
| moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], | ||
| coverageDirectory: '../../../../coverage/libs/domains/service-helm/feature', |
There was a problem hiding this comment.
Jest config appears copy/pasted from the Helm feature: displayName and coverageDirectory reference service-helm instead of service-job. This will mislabel test runs and write coverage to the wrong folder.
| displayName: 'domains-service-helm-feature', | |
| preset: '../../../../jest.preset.js', | |
| transform: { | |
| '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', | |
| '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], | |
| }, | |
| moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], | |
| coverageDirectory: '../../../../coverage/libs/domains/service-helm/feature', | |
| displayName: 'domains-service-job-feature', | |
| preset: '../../../../jest.preset.js', | |
| transform: { | |
| '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', | |
| '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], | |
| }, | |
| moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], | |
| coverageDirectory: '../../../../coverage/libs/domains/service-job/feature', |
| import { HelmCreationFlow, useHelmCreateContext } from '@qovery/domains/service-helm/feature' | ||
| import { renderWithProviders, screen } from '@qovery/shared/util-tests' | ||
|
|
||
| const mockNavigate = jest.fn() | ||
| const mockSearch = { | ||
| template: 'kubecost', | ||
| } | ||
|
|
||
| jest.mock('@qovery/shared/assistant/feature', () => ({ | ||
| AssistantTrigger: () => null, | ||
| })) | ||
|
|
||
| jest.mock('@tanstack/react-router', () => ({ | ||
| ...jest.requireActual('@tanstack/react-router'), | ||
| useParams: () => ({ | ||
| organizationId: 'org-1', | ||
| projectId: 'proj-1', | ||
| environmentId: 'env-1', | ||
| }), | ||
| useNavigate: () => mockNavigate, | ||
| useSearch: () => mockSearch, | ||
| })) | ||
|
|
||
| function ContextConsumer() { | ||
| const { currentStep, creationFlowUrl, generalForm, valuesOverrideFileForm, valuesOverrideArgumentsForm } = | ||
| useHelmCreateContext() | ||
|
|
||
| return ( | ||
| <div data-testid="context-consumer"> | ||
| step={currentStep} url={creationFlowUrl} name={generalForm.getValues('name')} icon= | ||
| {generalForm.getValues('icon_uri')} valuesType={valuesOverrideFileForm.getValues('type')} argumentsCount= | ||
| {valuesOverrideArgumentsForm.getValues('arguments').length} | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| describe('HelmCreationFlow', () => { | ||
| it('renders and provides helm creation context', () => { | ||
| renderWithProviders( | ||
| <HelmCreationFlow creationFlowUrl="/create/helm"> | ||
| <ContextConsumer /> | ||
| </HelmCreationFlow> |
There was a problem hiding this comment.
This spec file is for the new JobCreationFlow, but it currently imports and tests HelmCreationFlow/useHelmCreateContext (and the describe block is HelmCreationFlow). This looks like a copy/paste mistake and means the job creation flow isn’t being tested here. Update the test to exercise JobCreationFlow/useJobCreateContext and validate the job-specific default context values.
| export const SERVICES_CRONJOB_CREATION_URL = '/create/cron-job' | ||
| export const SERVICES_LIFECYCLE_CREATION_URL = '/create/lifecyle-job' | ||
| export const SERVICES_LIFECYCLE_TEMPLATE_CREATION_URL = (slug = ':slug', option = ':option') => | ||
| `/create/lifecyle-job/${slug}/${option}` | ||
| export const SERVICES_LIFECYCLE_TEMPLATE_CREATION_URL = (template?: string, option?: string) => { | ||
| const params = new URLSearchParams() | ||
| if (template) params.set('template', template) | ||
| if (option && option !== 'current') params.set('option', option) | ||
| const qs = params.toString() | ||
| return `/service/create/lifecycle-job${qs ? `?${qs}` : ''}` |
There was a problem hiding this comment.
SERVICES_LIFECYCLE_TEMPLATE_CREATION_URL now returns an absolute /service/create/... path (and SERVICES_LIFECYCLE_CREATION_URL still uses the misspelled /create/lifecyle-job). This breaks existing call sites that compose these as suffixes after SERVICES_URL() (e.g. apps/console/src/app/router/main.router.tsx:122), producing paths like /.../services/service/create/.... Consider keeping these constants consistently as suffixes (e.g. /create/lifecycle-job), or rename/split into a v5-only helper and update all call sites accordingly.
| </Button> | ||
| <Button datatest-id="button-submit" type="submit" disabled={!formState.isValid} size="lg"> | ||
| Continue |
There was a problem hiding this comment.
The submit button uses datatest-id, but the testing attribute used elsewhere is data-testid. With the current prop, tests querying by test id won’t find this button.
| const cloneData = { | ||
| ...data, | ||
| } | ||
|
|
||
| if (data.is_public_repository) { | ||
| data.auto_deploy = false | ||
| } | ||
|
|
||
| if (data.cmd_arguments) { | ||
| cloneData.cmd = parseCmd(data.cmd_arguments) | ||
| } | ||
|
|
||
| if (data.serviceType === 'CONTAINER') { | ||
| dockerfileForm.setValue('dockerfile_path', undefined) | ||
| dockerfileForm.setValue('dockerfile_raw', undefined) | ||
| dockerfileForm.setValue('docker_target_build_stage', undefined) | ||
| } | ||
| setGeneralData(cloneData) |
There was a problem hiding this comment.
In onSubmit, data.auto_deploy is mutated when is_public_repository is true, but the context is updated with cloneData, which still contains the original auto_deploy value. This means public repositories can incorrectly keep auto-deploy enabled. Update cloneData.auto_deploy (or avoid mutating data and only mutate cloneData) before calling setGeneralData.
| const location = useLocation() | ||
| const navigate = useNavigate() | ||
| const jobCreationSteps = getJobCreationSteps(ServiceTypeEnum.LIFECYCLE_JOB) | ||
|
|
There was a problem hiding this comment.
jobCreationSteps is initialized with getJobCreationSteps(ServiceTypeEnum.LIFECYCLE_JOB) regardless of the actual jobType. Since this flow is also used for cron jobs, the step titles (notably step 3) will be wrong. Compute steps from the current jobType state (and ensure it updates when jobType changes).
| const gotoGlobalInformations = useCallback(() => { | ||
| navigate({ | ||
| to: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/general', | ||
| params: { organizationId, projectId, environmentId }, | ||
| }) | ||
| }, [navigate, organizationId, projectId, environmentId]) | ||
|
|
||
| const gotoResources = useCallback(() => { | ||
| navigate({ | ||
| to: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/resources', | ||
| params: { organizationId, projectId, environmentId }, | ||
| }) | ||
| }, [navigate, organizationId, projectId, environmentId]) | ||
|
|
||
| const gotoConfigureJob = useCallback(() => { | ||
| navigate({ | ||
| to: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/configure', | ||
| params: { organizationId, projectId, environmentId }, | ||
| }) | ||
| }, [navigate, organizationId, projectId, environmentId]) | ||
|
|
||
| const gotoDockerfileJob = useCallback(() => { | ||
| navigate({ | ||
| to: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/dockerfile', | ||
| params: { organizationId, projectId, environmentId }, | ||
| }) | ||
| }, [navigate, organizationId, projectId, environmentId]) | ||
|
|
||
| const gotoVariable = useCallback(() => { | ||
| navigate({ | ||
| to: '/organization/$organizationId/project/$projectId/environment/$environmentId/service/create/lifecycle-job/variables', | ||
| params: { organizationId, projectId, environmentId }, | ||
| }) | ||
| }, [navigate, organizationId, projectId, environmentId]) | ||
|
|
There was a problem hiding this comment.
In StepSummary, the navigation callbacks are hard-coded to the .../service/create/lifecycle-job/... routes. This component is also used under the cron-job flow routes, so clicking the edit/back actions will incorrectly jump from cron-job creation into lifecycle-job creation. Use jobURL (creationFlowUrl) as the base for these routes (e.g. jobURL + '/general', etc.), or branch on jobType to target the correct flow slug.
| <div className="py-2"> | ||
| <hr className="border-t border-dashed border-neutral-250" /> | ||
| </div> |
There was a problem hiding this comment.
The <ul> in the General information section contains a <div> wrapper for the dashed <hr>. A <ul> should only contain <li> elements; this is invalid HTML and can confuse screen readers. Convert that wrapper into an <li> (or move the separator outside the list).
| <li> | ||
| <strong className="font-medium">From raw Dockerfile:</strong> {} | ||
| {truncateText(props.dockerfileData.dockerfile_raw, 50)}... | ||
| </li> |
There was a problem hiding this comment.
There is a stray empty JSX expression ({}) after the “From raw Dockerfile:” label. It renders nothing but adds noise and can confuse future edits; remove the empty expression.
There was a problem hiding this comment.
I don't know why but the lifecycle icon has a 70% opacity modifier on it, it should really be on 100% opacity I think (also the case in the current console, but it's more telling in dark mode)
Probably on all creation flow but we have a token issue on the git provider icon in the "Application source" input
Whole summary page is missing tokens
Should use the new Qovery logo here!
Cron job
Whole summary page is missing tokens here also!

Summary
This pull request adds the creation flows for Lifecycle and CRON jobs
📺 https://www.loom.com/share/c89bb0637450425c9cb275ea54770c1e