diff --git a/.gitignore b/.gitignore
index be64ddfae02..19510c231d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,7 @@ node_modules
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
+.zed
# misc
/.sass-cache
@@ -66,4 +67,4 @@ testem.log
Thumbs.db
.nx/cache
-.nx/workspace-data
\ No newline at end of file
+.nx/workspace-data
diff --git a/apps/console/src/app/router/main.router.tsx b/apps/console/src/app/router/main.router.tsx
index 2c8b7697141..3272b9395f7 100644
--- a/apps/console/src/app/router/main.router.tsx
+++ b/apps/console/src/app/router/main.router.tsx
@@ -15,6 +15,7 @@ import {
PageHelmCreateFeature,
PageJobCreateFeature,
PageServices,
+ PageTerraformCreateFeature,
} from '@qovery/pages/services'
import { PageSettings } from '@qovery/pages/settings'
import { PageUser } from '@qovery/pages/user'
@@ -44,6 +45,7 @@ import {
SERVICES_HELM_TEMPLATE_CREATION_URL,
SERVICES_LIFECYCLE_CREATION_URL,
SERVICES_LIFECYCLE_TEMPLATE_CREATION_URL,
+ SERVICES_TERRAFORM_CREATION_URL,
SERVICES_URL,
SETTINGS_URL,
USER_URL,
@@ -178,6 +180,12 @@ export const ROUTER: RouterProps[] = [
protected: true,
layout: false,
},
+ {
+ path: `${SERVICES_URL()}${SERVICES_TERRAFORM_CREATION_URL}/*`,
+ component: ,
+ protected: true,
+ layout: false,
+ },
{
path: `${APPLICATION_URL()}/*`,
component: ,
diff --git a/libs/domains/service-terraform/data-access/.eslintrc.json b/libs/domains/service-terraform/data-access/.eslintrc.json
new file mode 100644
index 00000000000..632e9b0e222
--- /dev/null
+++ b/libs/domains/service-terraform/data-access/.eslintrc.json
@@ -0,0 +1,18 @@
+{
+ "extends": ["../../../../.eslintrc.json"],
+ "ignorePatterns": ["!**/*"],
+ "overrides": [
+ {
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.js", "*.jsx"],
+ "rules": {}
+ }
+ ]
+}
diff --git a/libs/domains/service-terraform/data-access/README.md b/libs/domains/service-terraform/data-access/README.md
new file mode 100644
index 00000000000..380a07af02f
--- /dev/null
+++ b/libs/domains/service-terraform/data-access/README.md
@@ -0,0 +1,3 @@
+# domains-service-terraform-data-access
+
+This library was generated with [Nx](https://nx.dev).
diff --git a/libs/domains/service-terraform/data-access/project.json b/libs/domains/service-terraform/data-access/project.json
new file mode 100644
index 00000000000..021e34982c3
--- /dev/null
+++ b/libs/domains/service-terraform/data-access/project.json
@@ -0,0 +1,9 @@
+{
+ "name": "domains-service-terraform-data-access",
+ "$schema": "../../../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "libs/domains/service-terraform/data-access/src",
+ "projectType": "library",
+ "tags": ["scope:domain", "type:data-access", "slice:services"],
+ "// targets": "to see all targets run: nx show project domains-service-terraform-data-access --web",
+ "targets": {}
+}
diff --git a/libs/domains/service-terraform/data-access/src/index.ts b/libs/domains/service-terraform/data-access/src/index.ts
new file mode 100644
index 00000000000..863eb222c60
--- /dev/null
+++ b/libs/domains/service-terraform/data-access/src/index.ts
@@ -0,0 +1 @@
+export * from './lib/domains-service-terraform-data-access'
diff --git a/libs/domains/service-terraform/data-access/src/lib/domains-service-terraform-data-access.ts b/libs/domains/service-terraform/data-access/src/lib/domains-service-terraform-data-access.ts
new file mode 100644
index 00000000000..96e80643e80
--- /dev/null
+++ b/libs/domains/service-terraform/data-access/src/lib/domains-service-terraform-data-access.ts
@@ -0,0 +1,16 @@
+import { type TerraformRequest, TerraformsApi } from 'qovery-typescript-axios'
+
+const terraformsApi = new TerraformsApi()
+
+export const mutations = {
+ async createTerraformService({
+ environmentId,
+ terraformRequest,
+ }: {
+ environmentId: string
+ terraformRequest: TerraformRequest
+ }) {
+ const response = await terraformsApi.createTerraform(environmentId, terraformRequest)
+ return response.data
+ },
+}
diff --git a/libs/domains/service-terraform/data-access/tsconfig.json b/libs/domains/service-terraform/data-access/tsconfig.json
new file mode 100644
index 00000000000..059cd816618
--- /dev/null
+++ b/libs/domains/service-terraform/data-access/tsconfig.json
@@ -0,0 +1,19 @@
+{
+ "extends": "../../../../tsconfig.base.json",
+ "compilerOptions": {
+ "module": "commonjs",
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ }
+ ]
+}
diff --git a/libs/domains/service-terraform/data-access/tsconfig.lib.json b/libs/domains/service-terraform/data-access/tsconfig.lib.json
new file mode 100644
index 00000000000..18f2d37a19a
--- /dev/null
+++ b/libs/domains/service-terraform/data-access/tsconfig.lib.json
@@ -0,0 +1,10 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../../dist/out-tsc",
+ "declaration": true,
+ "types": ["node"]
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
+}
diff --git a/libs/domains/service-terraform/feature/.babelrc b/libs/domains/service-terraform/feature/.babelrc
new file mode 100644
index 00000000000..1ea870ead41
--- /dev/null
+++ b/libs/domains/service-terraform/feature/.babelrc
@@ -0,0 +1,12 @@
+{
+ "presets": [
+ [
+ "@nx/react/babel",
+ {
+ "runtime": "automatic",
+ "useBuiltIns": "usage"
+ }
+ ]
+ ],
+ "plugins": []
+}
diff --git a/libs/domains/service-terraform/feature/.eslintrc.json b/libs/domains/service-terraform/feature/.eslintrc.json
new file mode 100644
index 00000000000..772a43d2783
--- /dev/null
+++ b/libs/domains/service-terraform/feature/.eslintrc.json
@@ -0,0 +1,18 @@
+{
+ "extends": ["plugin:@nx/react", "../../../../.eslintrc.json"],
+ "ignorePatterns": ["!**/*"],
+ "overrides": [
+ {
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.js", "*.jsx"],
+ "rules": {}
+ }
+ ]
+}
diff --git a/libs/domains/service-terraform/feature/README.md b/libs/domains/service-terraform/feature/README.md
new file mode 100644
index 00000000000..224cbc3c917
--- /dev/null
+++ b/libs/domains/service-terraform/feature/README.md
@@ -0,0 +1,7 @@
+# domains-service-terraform-feature
+
+This library was generated with [Nx](https://nx.dev).
+
+## Running unit tests
+
+Run `nx test domains-service-terraform-feature` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/domains/service-terraform/feature/jest.config.ts b/libs/domains/service-terraform/feature/jest.config.ts
new file mode 100644
index 00000000000..7ba31a6c900
--- /dev/null
+++ b/libs/domains/service-terraform/feature/jest.config.ts
@@ -0,0 +1,11 @@
+/* eslint-disable */
+export default {
+ 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',
+}
diff --git a/libs/domains/service-terraform/feature/project.json b/libs/domains/service-terraform/feature/project.json
new file mode 100644
index 00000000000..146bf84581a
--- /dev/null
+++ b/libs/domains/service-terraform/feature/project.json
@@ -0,0 +1,21 @@
+{
+ "name": "domains-service-terraform-feature",
+ "$schema": "../../../../node_modules/nx/schemas/project-schema.json",
+ "sourceRoot": "libs/domains/service-terraform/feature/src",
+ "projectType": "library",
+ "tags": ["scope:domain", "type:feature", "slice:service-terraform"],
+ "implicitDependencies": ["!shared-console-shared"],
+ "targets": {
+ "test": {
+ "options": {
+ "passWithNoTests": true
+ },
+ "configurations": {
+ "ci": {
+ "ci": true,
+ "coverage": true
+ }
+ }
+ }
+ }
+}
diff --git a/libs/domains/service-terraform/feature/src/index.ts b/libs/domains/service-terraform/feature/src/index.ts
new file mode 100644
index 00000000000..a29a794d8a8
--- /dev/null
+++ b/libs/domains/service-terraform/feature/src/index.ts
@@ -0,0 +1,3 @@
+export * from './lib/hooks/use-create-terraform-service/use-create-terraform-service'
+export * from './source-setting/source-setting'
+export * from './values-override-arguments-setting/values-override-arguments-setting'
diff --git a/libs/domains/service-terraform/feature/src/lib/hooks/use-create-terraform-service/use-create-terraform-service.ts b/libs/domains/service-terraform/feature/src/lib/hooks/use-create-terraform-service/use-create-terraform-service.ts
new file mode 100644
index 00000000000..e012624f5de
--- /dev/null
+++ b/libs/domains/service-terraform/feature/src/lib/hooks/use-create-terraform-service/use-create-terraform-service.ts
@@ -0,0 +1,23 @@
+import { useMutation, useQueryClient } from '@tanstack/react-query'
+import { mutations } from '@qovery/domains/service-terraform/data-access'
+import { queries } from '@qovery/state/util-queries'
+
+export function useCreateTerraformService() {
+ const queryClient = useQueryClient()
+
+ return useMutation(mutations.createTerraformService, {
+ onSuccess(_, { environmentId }) {
+ queryClient.invalidateQueries({
+ queryKey: queries.services.list(environmentId).queryKey,
+ })
+ },
+ meta: {
+ notifyOnSuccess: {
+ title: 'Your Terraform service has been created',
+ },
+ notifyOnError: true,
+ },
+ })
+}
+
+export default useCreateTerraformService
diff --git a/libs/domains/service-terraform/feature/src/source-setting/source-setting.tsx b/libs/domains/service-terraform/feature/src/source-setting/source-setting.tsx
new file mode 100644
index 00000000000..6f603ae1334
--- /dev/null
+++ b/libs/domains/service-terraform/feature/src/source-setting/source-setting.tsx
@@ -0,0 +1,38 @@
+import { Controller, useFormContext } from 'react-hook-form'
+import { InputSelect } from '@qovery/shared/ui'
+
+export function SourceSetting() {
+ const { control, resetField } = useFormContext()
+
+ return (
+
+ (
+ {
+ resetField('repository')
+ field.onChange(value)
+ }}
+ value={field.value}
+ error={error?.message}
+ />
+ )}
+ />
+
+ )
+}
+
+export default SourceSetting
diff --git a/libs/domains/service-terraform/feature/src/values-override-arguments-setting/values-override-arguments-setting.tsx b/libs/domains/service-terraform/feature/src/values-override-arguments-setting/values-override-arguments-setting.tsx
new file mode 100644
index 00000000000..0de2d0da0dc
--- /dev/null
+++ b/libs/domains/service-terraform/feature/src/values-override-arguments-setting/values-override-arguments-setting.tsx
@@ -0,0 +1,219 @@
+import { type HelmRequestAllOfSource } from 'qovery-typescript-axios'
+import { type PropsWithChildren } from 'react'
+import {
+ Controller,
+ type UseFieldArrayRemove,
+ type UseFormReturn,
+ useFieldArray,
+ useFormContext,
+} from 'react-hook-form'
+import { useParams } from 'react-router-dom'
+import { FieldVariableSuggestion } from '@qovery/domains/variables/feature'
+import { Button, Heading, Icon, InputTextSmall, Section } from '@qovery/shared/ui'
+
+export interface TerraformValuesArgumentsData {
+ tf_var_file_paths: string[]
+ tf_vars: {
+ key: string
+ value: string
+ }[]
+}
+
+export interface ValuesOverrideArgumentsSettingProps extends PropsWithChildren {
+ source: HelmRequestAllOfSource
+ methods: UseFormReturn
+ onSubmit: () => void
+}
+
+function VarRow({ index, remove }: { index: number; remove: UseFieldArrayRemove }) {
+ const { environmentId = '' } = useParams()
+ const { control } = useFormContext()
+
+ return (
+
+
+
(
+
+ )}
+ />
+
+ (
+
+ )}
+ />
+
+
+
+
+
+
+ )
+}
+
+function PathRow({
+ field,
+ index,
+ tfPathsRemove,
+}: {
+ field: Record<'id', string>
+ index: number
+ tfPathsRemove: (index: number) => void
+}) {
+ const { control } = useFormContext()
+
+ return (
+
+
+ (
+
+ )}
+ />
+
+
+
+
+ )
+}
+
+export function ValuesOverrideArgumentsSetting({ methods, children, onSubmit }: ValuesOverrideArgumentsSettingProps) {
+ const {
+ fields: tfVars,
+ append: tfVarsAppend,
+ remove: tfVarsRemove,
+ } = useFieldArray({
+ control: methods.control,
+ name: 'tf_vars',
+ })
+ const {
+ fields: tfPaths,
+ append: tfPathsAppend,
+ remove: tfPathsRemove,
+ } = useFieldArray({
+ name: 'tf_var_file_paths',
+ })
+
+ return (
+
+ )
+}
+
+export default ValuesOverrideArgumentsSetting
diff --git a/libs/domains/service-terraform/feature/tsconfig.json b/libs/domains/service-terraform/feature/tsconfig.json
new file mode 100644
index 00000000000..c88d07daddd
--- /dev/null
+++ b/libs/domains/service-terraform/feature/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowJs": false,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ },
+ {
+ "path": "./tsconfig.spec.json"
+ }
+ ],
+ "extends": "../../../../tsconfig.base.json"
+}
diff --git a/libs/domains/service-terraform/feature/tsconfig.lib.json b/libs/domains/service-terraform/feature/tsconfig.lib.json
new file mode 100644
index 00000000000..1547ea009d1
--- /dev/null
+++ b/libs/domains/service-terraform/feature/tsconfig.lib.json
@@ -0,0 +1,23 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../../dist/out-tsc",
+ "types": ["node"]
+ },
+ "files": [
+ "../../../../node_modules/@nx/react/typings/cssmodule.d.ts",
+ "../../../../node_modules/@nx/react/typings/image.d.ts"
+ ],
+ "exclude": [
+ "jest.config.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.js",
+ "src/**/*.test.js",
+ "src/**/*.spec.jsx",
+ "src/**/*.test.jsx"
+ ],
+ "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
+}
diff --git a/libs/domains/service-terraform/feature/tsconfig.spec.json b/libs/domains/service-terraform/feature/tsconfig.spec.json
new file mode 100644
index 00000000000..1033686367b
--- /dev/null
+++ b/libs/domains/service-terraform/feature/tsconfig.spec.json
@@ -0,0 +1,20 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"]
+ },
+ "include": [
+ "jest.config.ts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.js",
+ "src/**/*.spec.js",
+ "src/**/*.test.jsx",
+ "src/**/*.spec.jsx",
+ "src/**/*.d.ts"
+ ]
+}
diff --git a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts
index b1edafe478f..91113c21c7b 100644
--- a/libs/domains/services/data-access/src/lib/domains-services-data-access.ts
+++ b/libs/domains/services/data-access/src/lib/domains-services-data-access.ts
@@ -12,6 +12,7 @@ import {
ApplicationMainCallsApi,
type ApplicationRequest,
ApplicationsApi,
+ type CheckedCustomDomainResponse,
type CleanFailedJobsRequest,
ContainerActionsApi,
type ContainerAdvancedSettings,
@@ -59,14 +60,23 @@ import {
JobsApi,
type RebootServicesRequest,
type Status,
+ TerraformActionsApi,
+ type TerraformAdvancedSettings,
TerraformConfigurationApi,
+ type TerraformDeployRequest,
+ TerraformDeploymentHistoryApi,
+ TerraformDeploymentRestrictionApi,
+ type TerraformDeploymentRestrictionRequest,
TerraformMainCallsApi,
+ type TerraformRequest,
+ TerraformsApi,
type Application as _Application,
type CloneServiceRequest as _CloneServiceRequest,
type ContainerResponse as _Container,
type Database as _Database,
type HelmResponse as _Helm,
type JobResponse as _Job,
+ type TerraformResponse as _Terraform,
} from 'qovery-typescript-axios'
import { type ApplicationStatusDto, type DatabaseStatusDto, type ServiceMetricsDto } from 'qovery-ws-typescript-axios'
import { match } from 'ts-pattern'
@@ -80,6 +90,7 @@ const containersApi = new ContainersApi()
const databasesApi = new DatabasesApi()
const jobsApi = new JobsApi()
const helmsApi = new HelmsApi()
+const terraformsApi = new TerraformsApi()
const applicationMainCallsApi = new ApplicationMainCallsApi()
const containerMainCallsApi = new ContainerMainCallsApi()
@@ -92,17 +103,20 @@ const terraformMainCallsApi = new TerraformMainCallsApi()
const applicationDeploymentRestrictionApi = new ApplicationDeploymentRestrictionApi()
const jobDeploymentRestrictionApi = new JobDeploymentRestrictionApi()
const helmDeploymentRestrictionApi = new HelmDeploymentRestrictionApi()
+const terraformDeploymentRestrictionApi = new TerraformDeploymentRestrictionApi()
const applicationDeploymentsApi = new ApplicationDeploymentHistoryApi()
const containerDeploymentsApi = new ContainerDeploymentHistoryApi()
const databaseDeploymentsApi = new DatabaseDeploymentHistoryApi()
const helmDeploymentsApi = new HelmDeploymentHistoryApi()
+const terraformDeploymentsApi = new TerraformDeploymentHistoryApi()
const jobDeploymentsApi = new JobDeploymentHistoryApi()
const applicationActionsApi = new ApplicationActionsApi()
const containerActionsApi = new ContainerActionsApi()
const databaseActionsApi = new DatabaseActionsApi()
const helmActionsApi = new HelmActionsApi()
+const terraformActionsApi = new TerraformActionsApi()
const jobActionsApi = new JobActionsApi()
const applicationConfigurationApi = new ApplicationConfigurationApi()
@@ -128,6 +142,7 @@ export type ContainerType = Extract
export type DatabaseType = Extract
export type JobType = Extract
export type HelmType = Extract
+export type TerraformType = Extract
// XXX: Need to remove `serviceType` and use only `service_type` since the the API now supports it.
// Waiting to have this implementation available in the edition interfaces.
@@ -151,8 +166,12 @@ export type Helm = _Helm & {
// @deprecated Prefer use `service_type` from API instead of `serviceType`
serviceType: HelmType
}
+export type Terraform = _Terraform & {
+ // @deprecated Prefer use `service_type` from API instead of `serviceType`
+ serviceType: TerraformType
+}
-export type AnyService = Application | Database | Container | Job | Helm
+export type AnyService = Application | Database | Container | Job | Helm | Terraform
export type AdvancedSettings =
| ApplicationAdvancedSettings
@@ -276,8 +295,10 @@ export const services = createQueryKeys('services', {
.with('HELM', () =>
helmDeploymentRestrictionApi.getHelmDeploymentRestrictions.bind(helmDeploymentRestrictionApi)
)
+ .with('TERRAFORM', () =>
+ terraformDeploymentRestrictionApi.getTerraformDeploymentRestrictions.bind(terraformDeploymentRestrictionApi)
+ )
.with('CONTAINER', 'DATABASE', () => null)
- .with('TERRAFORM', () => null) // TODO [QOV-821] double check that
.exhaustive()
if (!fn) {
throw new Error(`deploymentRestrictions unsupported for serviceType: ${serviceType}`)
@@ -322,7 +343,7 @@ export const services = createQueryKeys('services', {
props:
| {
serviceId: string
- serviceType: Extract
+ serviceType: Extract
}
| {
serviceId: string
@@ -352,6 +373,12 @@ export const services = createQueryKeys('services', {
serviceType,
}
})
+ .with({ serviceType: 'TERRAFORM' }, async ({ serviceId, serviceType }) => {
+ return {
+ results: (await terraformMainCallsApi.listTerraformCommit(serviceId)).data.results,
+ serviceType,
+ }
+ })
.exhaustive()
return commits
},
@@ -379,7 +406,10 @@ export const services = createQueryKeys('services', {
async () => (await jobDeploymentsApi.listJobDeploymentHistoryV2(serviceId)).data.results
)
.with('HELM', async () => (await helmDeploymentsApi.listHelmDeploymentHistoryV2(serviceId)).data.results)
- .with('TERRAFORM', async () => undefined) // TODO [QOV-821] to be implemented
+ .with(
+ 'TERRAFORM',
+ async () => (await terraformDeploymentsApi.listTerraformDeploymentHistoryV2(serviceId)).data.results
+ )
.exhaustive()
},
}),
@@ -448,7 +478,7 @@ export const services = createQueryKeys('services', {
}))
.with('HELM', (serviceType) => ({ query: helmsApi.getDefaultHelmAdvancedSettings.bind(helmsApi), serviceType }))
.with('TERRAFORM', (serviceType) => ({
- query: async () => ({ data: {} }), // TODO [QOV-821] to be implemented
+ query: terraformsApi.getDefaultTerraformAdvancedSettings.bind(terraformConfigurationApi),
serviceType,
}))
.exhaustive()
@@ -485,7 +515,7 @@ export const services = createQueryKeys('services', {
.with('TERRAFORM', (serviceType) => ({
query: terraformConfigurationApi.getTerraformAdvancedSettings.bind(terraformConfigurationApi),
serviceType,
- })) // TODO [QOV-821] to double check
+ }))
.exhaustive()
const response = await query(serviceId)
return response.data
@@ -496,7 +526,7 @@ export const services = createQueryKeys('services', {
serviceType,
}: {
serviceId: string
- serviceType: Extract
+ serviceType: Extract
}) => ({
queryKey: [serviceId],
async queryFn() {
@@ -513,6 +543,10 @@ export const services = createQueryKeys('services', {
query: customDomainHelmApi.listHelmCustomDomain.bind(customDomainHelmApi),
serviceType,
}))
+ .with('TERRAFORM', (serviceType) => ({
+ query: customDomainHelmApi.listHelmCustomDomain.bind(customDomainHelmApi),
+ serviceType,
+ })) // TODO [QOV-821] replace with customDomainTerraformApi when it will be available
.exhaustive()
const response = await query(serviceId)
return response.data.results
@@ -523,7 +557,7 @@ export const services = createQueryKeys('services', {
serviceType,
}: {
serviceId: string
- serviceType: Extract
+ serviceType: Extract
}) => ({
queryKey: [serviceId],
async queryFn() {
@@ -540,6 +574,10 @@ export const services = createQueryKeys('services', {
query: customDomainHelmApi.checkHelmCustomDomain.bind(customDomainHelmApi),
serviceType,
}))
+ .with('TERRAFORM', (serviceType) => ({
+ query: async () => ({ data: { results: [] as CheckedCustomDomainResponse[] } }), // TODO [QOV-821] to be implemented
+ serviceType,
+ }))
.exhaustive()
const response = await query(serviceId)
return response.data.results
@@ -572,6 +610,12 @@ type DeploymentRestrictionRequest =
deploymentRestrictionId: string
payload: HelmDeploymentRestrictionRequest
}
+ | {
+ serviceId: string
+ serviceType: TerraformType
+ deploymentRestrictionId: string
+ payload: TerraformDeploymentRestrictionRequest
+ }
type CreateServiceRequest = {
environmentId: string
@@ -591,6 +635,9 @@ type CreateServiceRequest = {
| ({
serviceType: HelmType
} & HelmRequest)
+ | ({
+ serviceType: TerraformType
+ } & TerraformRequest)
}
type EditServiceRequest = {
@@ -611,6 +658,9 @@ type EditServiceRequest = {
| ({
serviceType: HelmType
} & HelmRequest)
+ | ({
+ serviceType: TerraformType
+ } & TerraformRequest)
}
type DeployRequest =
@@ -639,6 +689,11 @@ type DeployRequest =
serviceId: string
serviceType: DatabaseType
}
+ | {
+ serviceId: string
+ serviceType: TerraformType
+ request?: TerraformDeployRequest
+ }
type EditAdvancedSettingsRequest = {
serviceId: string
@@ -655,6 +710,9 @@ type EditAdvancedSettingsRequest = {
| ({
serviceType: HelmType
} & HelmAdvancedSettings)
+ | ({
+ serviceType: TerraformType
+ } & TerraformAdvancedSettings)
}
export const mutations = {
@@ -672,9 +730,9 @@ export const mutations = {
}))
.with('HELM', (serviceType) => ({ mutation: helmsApi.cloneHelm.bind(helmsApi), serviceType }))
.with('TERRAFORM', (serviceType) => ({
- mutation: () => ({ data: { id: 'id', environment: { id: 'id' } } }),
+ mutation: terraformsApi.cloneTerraform.bind(terraformsApi),
serviceType,
- })) // TODO [QOV-821] to be implemented
+ }))
.exhaustive()
const response = await mutation(serviceId, payload)
return response.data
@@ -700,6 +758,12 @@ export const mutations = {
mutation: helmDeploymentRestrictionApi.editHelmDeploymentRestriction.bind(helmDeploymentRestrictionApi),
serviceType,
}))
+ .with('TERRAFORM', (serviceType) => ({
+ mutation: terraformDeploymentRestrictionApi.editTerraformDeploymentRestriction.bind(
+ terraformDeploymentRestrictionApi
+ ),
+ serviceType,
+ }))
.exhaustive()
const response = await mutation(serviceId, deploymentRestrictionId, payload)
return response.data
@@ -724,6 +788,12 @@ export const mutations = {
mutation: helmDeploymentRestrictionApi.createHelmDeploymentRestriction.bind(helmDeploymentRestrictionApi),
serviceType,
}))
+ .with('TERRAFORM', (serviceType) => ({
+ mutation: terraformDeploymentRestrictionApi.createTerraformDeploymentRestriction.bind(
+ terraformDeploymentRestrictionApi
+ ),
+ serviceType,
+ }))
.exhaustive()
const response = await mutation(serviceId, payload)
return response.data
@@ -748,6 +818,12 @@ export const mutations = {
mutation: helmDeploymentRestrictionApi.deleteHelmDeploymentRestriction.bind(helmDeploymentRestrictionApi),
serviceType,
}))
+ .with('TERRAFORM', (serviceType) => ({
+ mutation: terraformDeploymentRestrictionApi.deleteTerraformDeploymentRestriction.bind(
+ terraformDeploymentRestrictionApi
+ ),
+ serviceType,
+ }))
.exhaustive()
const response = await mutation(serviceId, deploymentRestrictionId)
return response.data
@@ -784,7 +860,7 @@ export const mutations = {
.with('TERRAFORM', (serviceType) => ({
mutation: terraformMainCallsApi.deleteTerraform.bind(terraformMainCallsApi),
serviceType,
- })) // TODO [QOV-821] double check that
+ }))
.exhaustive()
const response = await mutation(serviceId)
return response.data
@@ -811,6 +887,11 @@ export const mutations = {
mutation: helmsApi.createHelm.bind(helmsApi, environmentId, payload),
serviceType: 'HELM' as const,
}))
+ .with({ serviceType: 'TERRAFORM' }, (payload) => ({
+ mutation: terraformsApi.createTerraform.bind(terraformsApi, environmentId, payload),
+ serviceType: 'TERRAFORM' as const,
+ }))
+
.exhaustive()
const response = await mutation()
return response.data
@@ -837,6 +918,10 @@ export const mutations = {
mutation: helmMainCallsApi.editHelm.bind(helmMainCallsApi, serviceId, payload),
serviceType,
}))
+ .with({ serviceType: 'TERRAFORM' }, ({ serviceType, ...payload }) => ({
+ mutation: terraformMainCallsApi.editTerraform.bind(terraformMainCallsApi, serviceId, payload),
+ serviceType,
+ }))
.exhaustive()
const response = await mutation()
return response.data
@@ -901,6 +986,10 @@ export const mutations = {
mutation: helmActionsApi.deployHelm.bind(helmActionsApi, serviceId, undefined, request),
serviceType,
}))
+ .with({ serviceType: 'TERRAFORM' }, ({ serviceId, serviceType, request }) => ({
+ mutation: terraformActionsApi.deployTerraform.bind(terraformActionsApi, serviceId, request),
+ serviceType,
+ }))
.exhaustive()
const response = await mutation()
return response.data
@@ -976,6 +1065,14 @@ export const mutations = {
mutation: helmConfigurationApi.editHelmAdvancedSettings.bind(helmConfigurationApi, serviceId, payload),
serviceType,
}))
+ .with({ serviceType: 'TERRAFORM' }, ({ serviceType, ...payload }) => ({
+ mutation: terraformConfigurationApi.editTerraformAdvancedSettings.bind(
+ terraformConfigurationApi,
+ serviceId,
+ payload
+ ),
+ serviceType,
+ }))
.exhaustive()
const response = await mutation()
return response.data
diff --git a/libs/domains/services/feature/src/lib/hooks/use-deployment-restrictions/use-deployment-restrictions.ts b/libs/domains/services/feature/src/lib/hooks/use-deployment-restrictions/use-deployment-restrictions.ts
index b3d0d361935..5d003b258f7 100644
--- a/libs/domains/services/feature/src/lib/hooks/use-deployment-restrictions/use-deployment-restrictions.ts
+++ b/libs/domains/services/feature/src/lib/hooks/use-deployment-restrictions/use-deployment-restrictions.ts
@@ -1,10 +1,15 @@
import { useQuery } from '@tanstack/react-query'
-import { type ApplicationType, type HelmType, type JobType } from '@qovery/domains/services/data-access'
+import {
+ type ApplicationType,
+ type HelmType,
+ type JobType,
+ type TerraformType,
+} from '@qovery/domains/services/data-access'
import { queries } from '@qovery/state/util-queries'
export interface UseDeploymentRestrictionsProps {
serviceId: string
- serviceType: ApplicationType | JobType | HelmType
+ serviceType: ApplicationType | JobType | HelmType | TerraformType
}
export function useDeploymentRestrictions({ serviceId, serviceType }: UseDeploymentRestrictionsProps) {
diff --git a/libs/domains/services/feature/src/lib/hooks/use-deployment-status/use-deployment-status.ts b/libs/domains/services/feature/src/lib/hooks/use-deployment-status/use-deployment-status.ts
index 15376d94493..73c9d770fb8 100644
--- a/libs/domains/services/feature/src/lib/hooks/use-deployment-status/use-deployment-status.ts
+++ b/libs/domains/services/feature/src/lib/hooks/use-deployment-status/use-deployment-status.ts
@@ -27,6 +27,7 @@ export function useDeploymentStatus({ environmentId, serviceId }: UseDeploymentS
...(environmentStatus?.containers ?? []),
...(environmentStatus?.databases ?? []),
...(environmentStatus?.jobs ?? []),
+ ...(environmentStatus?.terraforms ?? []),
]
return webSocketResult.data
? webSocketResult
diff --git a/libs/domains/services/feature/src/lib/last-commit/last-commit.tsx b/libs/domains/services/feature/src/lib/last-commit/last-commit.tsx
index 85f0de3242f..c344e6c9639 100644
--- a/libs/domains/services/feature/src/lib/last-commit/last-commit.tsx
+++ b/libs/domains/services/feature/src/lib/last-commit/last-commit.tsx
@@ -1,6 +1,6 @@
import { type ApplicationGitRepository } from 'qovery-typescript-axios'
import { type MouseEvent, useState } from 'react'
-import { type Application, type Helm, type Job } from '@qovery/domains/services/data-access'
+import { type Application, type Helm, type Job, type Terraform } from '@qovery/domains/services/data-access'
import { Button, CopyToClipboard, Icon, Tooltip, Truncate, useModal } from '@qovery/shared/ui'
import { useDeployService } from '../hooks/use-deploy-service/use-deploy-service'
import { useLastDeployedCommit } from '../hooks/use-last-deployed-commit/use-last-deployed-commit'
@@ -9,7 +9,7 @@ import SelectCommitModal from '../select-commit-modal/select-commit-modal'
export interface LastCommitProps {
organizationId: string
projectId: string
- service: Pick
+ service: Pick
gitRepository: ApplicationGitRepository
}
diff --git a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx
index 5c7a60d7a97..1072b471fc0 100644
--- a/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx
+++ b/libs/domains/services/feature/src/lib/service-action-toolbar/service-action-toolbar.tsx
@@ -53,6 +53,7 @@ import {
isCancelBuildAvailable,
isDeleteAvailable,
isDeployAvailable,
+ isDryRunAvailable,
isRedeployAvailable,
isRestartAvailable,
isStopAvailable,
@@ -123,6 +124,10 @@ function MenuManageDeployment({
)
const mutationDeploy = () => deployService({ serviceId: service.id, serviceType: service.serviceType })
+ const mutationDryRun = () => {
+ if (service.serviceType !== 'TERRAFORM') return
+ deployService({ serviceId: service.id, serviceType: service.serviceType, request: { dry_run: true } })
+ }
const mutationRedeploy = () => {
openModalConfirmation({
@@ -344,6 +349,11 @@ function MenuManageDeployment({
{state === StateEnum.DELETE_QUEUED || state === StateEnum.DELETING ? 'Cancel delete' : 'Cancel deployment'}
)}
+ {isDryRunAvailable(service.serviceType) && (
+ } onSelect={mutationDryRun}>
+ Dry run
+
+ )}
{isDeployAvailable(state) && (
}
@@ -550,6 +560,7 @@ function MenuManageDeployment({
}
)
.with({ service: { serviceType: 'DATABASE' } }, () => null)
+ .with({ service: { serviceType: 'TERRAFORM' } }, () => null) // TODO [QOV-821] double check that
.exhaustive()}
{match(service)
.with({ serviceType: 'HELM', values_override: P.when(isHelmGitValuesOverride) }, (service) => {
diff --git a/libs/domains/services/feature/src/lib/service-avatar/service-avatar.tsx b/libs/domains/services/feature/src/lib/service-avatar/service-avatar.tsx
index deacdf1186c..3504f4b26aa 100644
--- a/libs/domains/services/feature/src/lib/service-avatar/service-avatar.tsx
+++ b/libs/domains/services/feature/src/lib/service-avatar/service-avatar.tsx
@@ -1,4 +1,3 @@
-import { type ServiceTypeEnum } from 'qovery-typescript-axios'
import { type ComponentPropsWithoutRef, type ElementRef, forwardRef } from 'react'
import { match } from 'ts-pattern'
import { type AnyService } from '@qovery/domains/services/data-access'
diff --git a/libs/domains/services/feature/src/lib/service-clone-modal/service-clone-modal.tsx b/libs/domains/services/feature/src/lib/service-clone-modal/service-clone-modal.tsx
index 29178b1483a..21f21e54837 100644
--- a/libs/domains/services/feature/src/lib/service-clone-modal/service-clone-modal.tsx
+++ b/libs/domains/services/feature/src/lib/service-clone-modal/service-clone-modal.tsx
@@ -96,6 +96,7 @@ export function ServiceCloneModal({ onClose, organizationId, projectId, serviceI
() => 'https://hub.qovery.com/docs/using-qovery/configuration/lifecylejob/#clone'
)
.with({ serviceType: 'HELM' }, () => 'https://hub.qovery.com/docs/using-qovery/configuration/helm/#clone')
+ .with({ serviceType: 'TERRAFORM' }, () => 'https://hub.qovery.com/docs/using-qovery/configuration/terraform/#clone') // TODO [QOV-821] replace URL with new doc path
.exhaustive()
return (
diff --git a/libs/domains/services/feature/src/lib/service-list/service-list.tsx b/libs/domains/services/feature/src/lib/service-list/service-list.tsx
index 7c964db8aa3..97a851812b8 100644
--- a/libs/domains/services/feature/src/lib/service-list/service-list.tsx
+++ b/libs/domains/services/feature/src/lib/service-list/service-list.tsx
@@ -21,7 +21,13 @@ import type {
import { type ComponentProps, Fragment, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { P, match } from 'ts-pattern'
-import { type AnyService, type Application, type Helm, type Job } from '@qovery/domains/services/data-access'
+import {
+ type AnyService,
+ type Application,
+ type Helm,
+ type Job,
+ type Terraform,
+} from '@qovery/domains/services/data-access'
import {
IconEnum,
ServiceTypeEnum,
@@ -454,7 +460,7 @@ export function ServiceList({ environment, className, ...props }: ServiceListPro
cell: (info) => {
const service = info.row.original
- const gitInfo = (service: Application | Job | Helm, gitRepository?: ApplicationGitRepository) =>
+ const gitInfo = (service: Application | Job | Helm | Terraform, gitRepository?: ApplicationGitRepository) =>
gitRepository && (
e.stopPropagation()}>
@@ -641,6 +647,10 @@ export function ServiceList({ environment, className, ...props }: ServiceListPro
},
}) => helmInfo(repository)
)
+ .with({ service: { serviceType: 'TERRAFORM' } }, ({ service }) => {
+ // @ts-expect-error Temporary fix for missing type
+ return gitInfo(service, service?.terraform_files_source?.git?.git_repository) // TODO [CQ-821] double check that
+ })
.exhaustive()
return cell
},
diff --git a/libs/pages/application/src/lib/feature/page-settings-deployment-restrictions-feature/crud-modal-feature/crud-modal-feature.tsx b/libs/pages/application/src/lib/feature/page-settings-deployment-restrictions-feature/crud-modal-feature/crud-modal-feature.tsx
index 9929823a5c8..ee9cba77e14 100644
--- a/libs/pages/application/src/lib/feature/page-settings-deployment-restrictions-feature/crud-modal-feature/crud-modal-feature.tsx
+++ b/libs/pages/application/src/lib/feature/page-settings-deployment-restrictions-feature/crud-modal-feature/crud-modal-feature.tsx
@@ -2,17 +2,23 @@ import {
type ApplicationDeploymentRestriction,
DeploymentRestrictionModeEnum,
DeploymentRestrictionTypeEnum,
+ type TerraformDeploymentRestrictionResponse,
} from 'qovery-typescript-axios'
import { FormProvider, useForm } from 'react-hook-form'
-import { type ApplicationType, type HelmType, type JobType } from '@qovery/domains/services/data-access'
+import {
+ type ApplicationType,
+ type HelmType,
+ type JobType,
+ type TerraformType,
+} from '@qovery/domains/services/data-access'
import { useCreateDeploymentRestriction, useEditDeploymentRestriction } from '@qovery/domains/services/feature'
import CrudModal from '../../../ui/page-settings-deployment-restrictions/crud-modal/crud-modal'
export interface CrudModalFeatureProps {
- deploymentRestriction?: ApplicationDeploymentRestriction
+ deploymentRestriction?: ApplicationDeploymentRestriction | TerraformDeploymentRestrictionResponse
onClose: () => void
serviceId: string
- serviceType: ApplicationType | JobType | HelmType
+ serviceType: ApplicationType | JobType | HelmType | TerraformType
}
export function CrudModalFeature({ deploymentRestriction, serviceId, serviceType, onClose }: CrudModalFeatureProps) {
const serviceParams = {
diff --git a/libs/pages/application/src/lib/feature/page-settings-deployment-restrictions-feature/page-settings-deployment-restrictions-feature.tsx b/libs/pages/application/src/lib/feature/page-settings-deployment-restrictions-feature/page-settings-deployment-restrictions-feature.tsx
index 13abe3e2c5e..ac2514d4608 100644
--- a/libs/pages/application/src/lib/feature/page-settings-deployment-restrictions-feature/page-settings-deployment-restrictions-feature.tsx
+++ b/libs/pages/application/src/lib/feature/page-settings-deployment-restrictions-feature/page-settings-deployment-restrictions-feature.tsx
@@ -1,6 +1,14 @@
-import { type ApplicationDeploymentRestriction } from 'qovery-typescript-axios'
+import {
+ type ApplicationDeploymentRestriction,
+ type TerraformDeploymentRestrictionResponse,
+} from 'qovery-typescript-axios'
import { useParams } from 'react-router-dom'
-import { type ApplicationType, type HelmType, type JobType } from '@qovery/domains/services/data-access'
+import {
+ type ApplicationType,
+ type HelmType,
+ type JobType,
+ type TerraformType,
+} from '@qovery/domains/services/data-access'
import {
useDeleteDeploymentRestriction,
useDeploymentRestrictions,
@@ -34,7 +42,8 @@ export function PageSettingsDeploymentRestrictionsFeature() {
return null
}
- const isValidServiceType = serviceType === 'APPLICATION' || serviceType === 'JOB' || serviceType === 'HELM'
+ const isValidServiceType =
+ serviceType === 'APPLICATION' || serviceType === 'JOB' || serviceType === 'HELM' || serviceType === 'TERRAFORM'
return (
@@ -74,7 +83,7 @@ export function PageSettingsDeploymentRestrictionsFeature() {
interface PageSettingsDeploymentRestrictionsFeatureInnerProps {
serviceId: string
- serviceType: ApplicationType | JobType | HelmType
+ serviceType: ApplicationType | JobType | HelmType | TerraformType
}
function PageSettingsDeploymentRestrictionsFeatureInner({
@@ -90,7 +99,9 @@ function PageSettingsDeploymentRestrictionsFeatureInner({
const { mutate: deleteRestriction } = useDeleteDeploymentRestriction()
const { openModal, closeModal } = useModal()
const { openModalConfirmation } = useModalConfirmation()
- const handleEdit = (deploymentRestriction: ApplicationDeploymentRestriction) => {
+ const handleEdit = (
+ deploymentRestriction: ApplicationDeploymentRestriction | TerraformDeploymentRestrictionResponse
+ ) => {
openModal({
content: (
@@ -98,7 +109,9 @@ function PageSettingsDeploymentRestrictionsFeatureInner({
})
}
- const handleDelete = (deploymentRestriction: ApplicationDeploymentRestriction) => {
+ const handleDelete = (
+ deploymentRestriction: ApplicationDeploymentRestriction | TerraformDeploymentRestrictionResponse
+ ) => {
openModalConfirmation({
title: 'Delete Restriction',
name: `${deploymentRestriction.mode}/${deploymentRestriction.type}/${deploymentRestriction.value}`,
diff --git a/libs/pages/application/src/lib/feature/page-settings-feature/page-settings-feature.tsx b/libs/pages/application/src/lib/feature/page-settings-feature/page-settings-feature.tsx
index 652be81300e..41d6fcfa988 100644
--- a/libs/pages/application/src/lib/feature/page-settings-feature/page-settings-feature.tsx
+++ b/libs/pages/application/src/lib/feature/page-settings-feature/page-settings-feature.tsx
@@ -161,6 +161,13 @@ export function PageSettingsFeature() {
advancedSettings,
dangerzoneSettings,
])
+ .with({ serviceType: 'TERRAFORM' }, () => [
+ generalSettings,
+ resourcesSettings,
+ deploymentRestrictionsSettings,
+ advancedSettings,
+ dangerzoneSettings,
+ ])
.with({ serviceType: 'JOB' }, (s) => [
generalSettings,
...(s.job_type === 'LIFECYCLE' && isJobGitSource(s.source) ? [dockerfileSetting] : []),
diff --git a/libs/pages/application/src/lib/feature/page-settings-resources-feature/page-settings-resources-feature.tsx b/libs/pages/application/src/lib/feature/page-settings-resources-feature/page-settings-resources-feature.tsx
index 867306c0548..226fe2b0b01 100644
--- a/libs/pages/application/src/lib/feature/page-settings-resources-feature/page-settings-resources-feature.tsx
+++ b/libs/pages/application/src/lib/feature/page-settings-resources-feature/page-settings-resources-feature.tsx
@@ -22,6 +22,9 @@ export function SettingsResourcesFeature({ service, environment }: SettingsResou
const defaultInstances = match(service)
.with({ serviceType: 'JOB' }, () => ({}))
+ .with({ serviceType: 'TERRAFORM' }, (s) => ({
+ storage_gib: s.job_resources.storage_gib,
+ }))
.otherwise((s) => ({
min_running_instances: s.min_running_instances || 1,
max_running_instances: s.max_running_instances || 1,
@@ -30,8 +33,12 @@ export function SettingsResourcesFeature({ service, environment }: SettingsResou
const methods = useForm({
mode: 'onChange',
defaultValues: {
- memory: service.memory,
- cpu: service.cpu,
+ memory: match(service)
+ .with({ serviceType: 'TERRAFORM' }, (s) => s.job_resources.ram_mib)
+ .otherwise((s) => s.memory || 0),
+ cpu: match(service)
+ .with({ serviceType: 'TERRAFORM' }, (s) => s.job_resources.cpu_milli)
+ .otherwise((s) => s.cpu || 0),
...defaultInstances,
},
})
@@ -67,6 +74,18 @@ export function SettingsResourcesFeature({ service, environment }: SettingsResou
request: requestWithInstances,
})
)
+ .with({ serviceType: 'TERRAFORM' }, (service) =>
+ buildEditServicePayload({
+ service,
+ request: {
+ job_resources: {
+ cpu_milli: Number(data['cpu']),
+ ram_mib: Number(data['memory']),
+ storage_gib: Number(data['storage_gib']),
+ },
+ },
+ })
+ )
.exhaustive()
editService({
@@ -75,7 +94,8 @@ export function SettingsResourcesFeature({ service, environment }: SettingsResou
})
})
- const displayWarningCpu: boolean = (methods.watch('cpu') || 0) > (service.maximum_cpu || 0)
+ const displayWarningCpu: boolean =
+ 'maximum_cpu' in service && (methods.watch('cpu') || 0) > (service.maximum_cpu || 0)
return (
@@ -98,9 +118,13 @@ export function PageSettingsResourcesFeature() {
if (!environment) return null
return match(service)
- .with({ serviceType: 'APPLICATION' }, { serviceType: 'CONTAINER' }, { serviceType: 'JOB' }, (service) => (
-
- ))
+ .with(
+ { serviceType: 'APPLICATION' },
+ { serviceType: 'CONTAINER' },
+ { serviceType: 'JOB' },
+ { serviceType: 'TERRAFORM' },
+ (service) =>
+ )
.otherwise(() => null)
}
diff --git a/libs/pages/application/src/lib/ui/page-settings-resources/__snapshots__/page-settings-resources.spec.tsx.snap b/libs/pages/application/src/lib/ui/page-settings-resources/__snapshots__/page-settings-resources.spec.tsx.snap
index 27bd64d33ab..e811faecfe8 100644
--- a/libs/pages/application/src/lib/ui/page-settings-resources/__snapshots__/page-settings-resources.spec.tsx.snap
+++ b/libs/pages/application/src/lib/ui/page-settings-resources/__snapshots__/page-settings-resources.spec.tsx.snap
@@ -84,10 +84,7 @@ exports[`PageSettingsResources should render warning box and icon for cpu 1`] =
10
milli vCPU.
- Maximum value allowed based on the selected cluster instance type:
- 10
- milli vCPU.
-
+ Maximum value allowed based on the selected cluster instance type: 10 milli vCPU.
diff --git a/libs/pages/application/src/lib/ui/page-settings-resources/page-settings-resources.spec.tsx b/libs/pages/application/src/lib/ui/page-settings-resources/page-settings-resources.spec.tsx
index fdaf4fa0184..5537af2afbc 100644
--- a/libs/pages/application/src/lib/ui/page-settings-resources/page-settings-resources.spec.tsx
+++ b/libs/pages/application/src/lib/ui/page-settings-resources/page-settings-resources.spec.tsx
@@ -19,6 +19,7 @@ jest.mock('react-hook-form', () => ({
watch: () => jest.fn(),
formState: {
isValid: true,
+ isDirty: true,
},
}),
}))
diff --git a/libs/pages/application/src/lib/ui/page-settings-resources/page-settings-resources.tsx b/libs/pages/application/src/lib/ui/page-settings-resources/page-settings-resources.tsx
index d1820724239..108c06dae94 100644
--- a/libs/pages/application/src/lib/ui/page-settings-resources/page-settings-resources.tsx
+++ b/libs/pages/application/src/lib/ui/page-settings-resources/page-settings-resources.tsx
@@ -22,7 +22,7 @@ export function PageSettingsResources(props: PageSettingsResourcesProps) {