-
Deploy latest version for..
-
- Select the services you want to update to the latest version
-
-
-
-
- For{' '}
-
-
-
+
+
+
Deploy by version
+
+ Select the service and choose a specific version to deploy on each of those
- {outdatedServices.length > 0 &&
- (selectedServiceIds.length > 0 ? (
-
- ) : (
-
- ))}
-
- {isOutdatedServicesLoading ? (
-
- ) : outdatedServices.length ? (
-
-
- {outdatedServices.map((application, index) => {
- const gitRepository = match(application)
- .with({ serviceType: 'APPLICATION' }, ({ git_repository }) => git_repository)
- .with(
- { serviceType: 'JOB', source: P.when(isJobGitSource) },
- ({ source }) => source.docker?.git_repository
- )
- .otherwise(() => undefined)
-
- return (
- - checkService(application.id)}
- key={application.id}
- className={`${index === 0 ? 'rounded-t' : ''} ${
- outdatedServices.length - 1 === index ? 'rounded-b !border-b' : ''
- } flex justify-between border border-b-0 p-4 dark:border-neutral-400 ${
- isChecked(application.id)
- ? `border border-brand-500 bg-brand-50 dark:bg-neutral-500`
- : 'border-neutral-250'
- } ${outdatedServices && isChecked(outdatedServices[index - 1]?.id) && 'border-t-brand-500'}`}
- >
-
-
-
-
-
-
e.stopPropagation()}
- >
- {}
-
-
-
-
- {application.commits && Boolean(application.commits.length) && (
-
e.stopPropagation()}
- >
-
-
-
- )}
-
-
- )
+ {isOutdatedServicesLoading || isServicesLoading || isLatestVersionsLoading ? (
+
+
+
+ ) : (
+
+ {renderSection({
+ title: `${outdatedCount} outdated ${serviceLabel(outdatedCount)}`,
+ servicesInSection: orderedServices.outdated,
+ showBulkActions: true,
})}
-
-
- ) : (
-
-
-
No outdated services found
-
- )}
+ {renderSection({
+ title: `${upToDateCount} up-to-date ${serviceLabel(upToDateCount)}`,
+ servicesInSection: orderedServices.upToDate,
+ })}
+ {renderSection({
+ title: `${deletedBranchCount} ${serviceLabel(deletedBranchCount)} on deleted branch`,
+ servicesInSection: orderedServices.deletedBranch,
+ })}
+
+ )}
+
-
+
diff --git a/libs/domains/environments/feature/src/lib/update-all-modal/update-all-modal.utils.ts b/libs/domains/environments/feature/src/lib/update-all-modal/update-all-modal.utils.ts
new file mode 100644
index 00000000000..dfa908039e4
--- /dev/null
+++ b/libs/domains/environments/feature/src/lib/update-all-modal/update-all-modal.utils.ts
@@ -0,0 +1,105 @@
+import { match } from 'ts-pattern'
+import { sortVersions } from '@qovery/domains/organizations/feature'
+import {
+ type ServiceForDeploy,
+ type ServiceVersionInfo,
+} from '../hooks/use-services-for-deploy/use-services-for-deploy'
+
+type CommitQueryServiceType = 'APPLICATION' | 'JOB' | 'HELM' | 'TERRAFORM'
+
+export function isSupportedUpdateAllService(service: ServiceForDeploy) {
+ return service.sourceType !== 'database'
+}
+
+export function isOutdatedManagedByHook(service: ServiceForDeploy) {
+ return service.sourceType === 'git' && (service.serviceType === 'APPLICATION' || service.serviceType === 'JOB')
+}
+
+export function getCommitQueryServiceType(service: ServiceForDeploy): CommitQueryServiceType | undefined {
+ if (service.sourceType !== 'git') return undefined
+
+ return match(service.serviceType)
+ .with('APPLICATION', () => 'APPLICATION' as const)
+ .with('JOB', 'CRON_JOB', 'LIFECYCLE_JOB', () => 'JOB' as const)
+ .with('HELM', () => 'HELM' as const)
+ .with('TERRAFORM', () => 'TERRAFORM' as const)
+ .otherwise(() => undefined)
+}
+
+export function getLatestVersionFromQueryData(
+ service: ServiceForDeploy,
+ data: unknown
+): ServiceVersionInfo | undefined {
+ if (!data) return undefined
+
+ return match(service.sourceType)
+ .with('git', () => {
+ const commits = data as { git_commit_id: string }[]
+ const latestCommitId = commits?.[0]?.git_commit_id
+ if (!latestCommitId) return undefined
+ return {
+ type: 'commit' as const,
+ value: latestCommitId,
+ displayValue: latestCommitId.slice(0, 7),
+ }
+ })
+ .with('container', () => {
+ const images = data as { versions?: string[] }[]
+ const versions = (sortVersions(images?.[0]?.versions ?? []) ?? []).filter((version) => version !== 'latest')
+ const latestTag = versions[0]
+ if (!latestTag) return undefined
+ return {
+ type: 'tag' as const,
+ value: latestTag,
+ displayValue: latestTag,
+ }
+ })
+ .with('helm-repository', () => {
+ const charts = data as { chart_name?: string; versions?: string[] }[]
+ const versions =
+ charts?.find(({ chart_name }) => chart_name === service.helmRepository?.chartName)?.versions?.slice() ?? []
+
+ const sortedVersions = sortVersions(versions) ?? []
+ const latestChartVersion = sortedVersions[0]
+ if (!latestChartVersion) return undefined
+ return {
+ type: 'chart-version' as const,
+ value: latestChartVersion,
+ displayValue: latestChartVersion,
+ }
+ })
+ .with('database', () => undefined)
+ .exhaustive()
+}
+
+export function isDeletedBranchFromQueryData(service: ServiceForDeploy, data: unknown) {
+ if (service.sourceType !== 'git') return false
+ if (!Array.isArray(data)) return false
+ return data.length === 0
+}
+
+export function getServiceOutdatedState({
+ service,
+ outdatedByHookVersion,
+ latestVersion,
+}: {
+ service: ServiceForDeploy
+ outdatedByHookVersion?: ServiceVersionInfo
+ latestVersion?: ServiceVersionInfo
+}) {
+ const outdatedByHook = Boolean(outdatedByHookVersion)
+ const outdatedByLatest =
+ Boolean(latestVersion?.value) &&
+ Boolean(service.currentVersion?.value) &&
+ latestVersion?.value !== service.currentVersion?.value
+
+ const isOutdated = outdatedByHook || outdatedByLatest
+ const latestOutdatedVersion = outdatedByHookVersion ?? (isOutdated ? latestVersion : undefined)
+ const defaultVersion = latestOutdatedVersion ?? latestVersion ?? service.currentVersion
+
+ return {
+ isOutdated,
+ latestOutdatedVersion,
+ defaultVersion,
+ }
+}
diff --git a/libs/domains/services/feature/src/lib/select-commit-modal/__snapshots__/select-commit-modal.spec.tsx.snap b/libs/domains/services/feature/src/lib/select-commit-modal/__snapshots__/select-commit-modal.spec.tsx.snap
index 0d654c8fdbd..c4f0b4b5e9f 100644
--- a/libs/domains/services/feature/src/lib/select-commit-modal/__snapshots__/select-commit-modal.spec.tsx.snap
+++ b/libs/domains/services/feature/src/lib/select-commit-modal/__snapshots__/select-commit-modal.spec.tsx.snap
@@ -1,4 +1,4 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`SelectCommitModal should match snapshot 1`] = `
@@ -62,7 +62,7 @@ exports[`SelectCommitModal should match snapshot 1`] = `
/>
Commit
on
- Jan 01, 0123
+ Jan 01, 2024
committed
- 1904 years
+ 2 years
ago
@@ -164,7 +164,7 @@ exports[`SelectCommitModal should match snapshot 1`] = `
/>
Commit
on
- Jan 01, 0456
+ Jan 02, 2024
committed
- 1571 years
+ 2 years
ago
diff --git a/libs/domains/services/feature/src/lib/select-commit-modal/select-commit-modal.spec.tsx b/libs/domains/services/feature/src/lib/select-commit-modal/select-commit-modal.spec.tsx
index 2eca6d3b54f..6eee8b290b3 100644
--- a/libs/domains/services/feature/src/lib/select-commit-modal/select-commit-modal.spec.tsx
+++ b/libs/domains/services/feature/src/lib/select-commit-modal/select-commit-modal.spec.tsx
@@ -1,23 +1,47 @@
import { renderWithProviders, screen } from '@qovery/shared/util-tests'
+import { useLastDeployedCommit } from '../hooks/use-last-deployed-commit/use-last-deployed-commit'
import { SelectCommitModal } from './select-commit-modal'
jest.mock('../hooks/use-last-deployed-commit/use-last-deployed-commit', () => {
return {
...jest.requireActual('../hooks/use-last-deployed-commit/use-last-deployed-commit'),
- useLastDeployedCommit: () => ({
+ useLastDeployedCommit: jest.fn(),
+ }
+})
+
+const mockedUseLastDeployedCommit = jest.mocked(useLastDeployedCommit)
+
+describe('SelectCommitModal', () => {
+ beforeEach(() => {
+ mockedUseLastDeployedCommit.mockReturnValue({
data: {
commits: [
- { created_at: '123', author_name: 'foo', git_commit_id: '123', message: 'lorem', tag: '' },
- { created_at: '456', author_name: 'foo', git_commit_id: '456', message: 'ipsum', tag: '' },
+ { created_at: '2024-01-01T00:00:00Z', author_name: 'foo', git_commit_id: '123', message: 'lorem', tag: '' },
+ { created_at: '2024-01-02T00:00:00Z', author_name: 'foo', git_commit_id: '456', message: 'ipsum', tag: '' },
],
delta: 1,
- deployedCommit: { created_at: '456', author_name: 'foo', git_commit_id: '456', message: 'ipsum', tag: '' },
+ deployedCommit: {
+ created_at: '2024-01-02T00:00:00Z',
+ author_name: 'foo',
+ git_commit_id: '456',
+ message: 'ipsum',
+ tag: '',
+ },
},
- }),
- }
-})
+ isLoading: false,
+ isError: false,
+ isFetching: false,
+ isSuccess: true,
+ error: null,
+ status: 'success',
+ fetchStatus: 'idle',
+ })
+ })
+
+ afterEach(() => {
+ jest.clearAllMocks()
+ })
-describe('SelectCommitModal', () => {
it('should match snapshot', () => {
const onCancel = jest.fn()
const onSubmit = jest.fn()
@@ -78,4 +102,45 @@ describe('SelectCommitModal', () => {
await userEvent.click(screen.getByRole('button', { name: /Deploy/i }))
expect(onSubmit).toHaveBeenCalledWith('123')
})
+
+ it('should show empty state and hide search when no commits are returned', () => {
+ mockedUseLastDeployedCommit.mockReturnValueOnce({
+ data: {
+ commits: [],
+ delta: 0,
+ deployedCommit: {
+ created_at: '2024-01-02T00:00:00Z',
+ author_name: 'foo',
+ git_commit_id: '456',
+ message: 'ipsum',
+ tag: '',
+ },
+ },
+ isLoading: false,
+ isError: false,
+ isFetching: false,
+ isSuccess: true,
+ error: null,
+ status: 'success',
+ fetchStatus: 'idle',
+ })
+
+ renderWithProviders(
+
+ For X service
+
+ )
+
+ expect(screen.queryByPlaceholderText(/search by commit message or commit id/i)).not.toBeInTheDocument()
+ expect(screen.getByText(/No commit available/i)).toBeInTheDocument()
+ })
})
diff --git a/libs/domains/services/feature/src/lib/select-commit-modal/select-commit-modal.tsx b/libs/domains/services/feature/src/lib/select-commit-modal/select-commit-modal.tsx
index 588f7e28d32..9b705d3c324 100644
--- a/libs/domains/services/feature/src/lib/select-commit-modal/select-commit-modal.tsx
+++ b/libs/domains/services/feature/src/lib/select-commit-modal/select-commit-modal.tsx
@@ -40,6 +40,7 @@ export function SelectCommitModal({
const [search, setSearch] = useState('')
const [targetCommitId, setTargetCommitId] = useState
()
+ const hasCommits = (data.commits?.length ?? 0) > 0
const commitsByDay = useMemo(
() =>
@@ -81,7 +82,7 @@ export function SelectCommitModal({
{children}
-