Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createFileRoute } from '@tanstack/react-router'
import { TerraformArgumentsSettings } from '@qovery/domains/service-settings/feature'

export const Route = createFileRoute(
'/_authenticated/organization/$organizationId/project/$projectId/environment/$environmentId/service/$serviceId/settings/terraform-arguments'
Expand All @@ -7,5 +8,5 @@ export const Route = createFileRoute(
})

function RouteComponent() {
return <div className="px-10 py-7">Terraform arguments</div>
return <TerraformArgumentsSettings />
}
1 change: 1 addition & 0 deletions libs/domains/service-settings/feature/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './lib/application-container-storage-settings/application-containe
export * from './lib/service-domain-settings/service-domain-settings/service-domain-settings'
export * from './lib/service-deployment-restrictions-settings/service-deployment-restrictions-settings/service-deployment-restrictions-settings'
export * from './lib/terraform-configuration-settings/terraform-configuration-settings'
export * from './lib/terraform-arguments-settings/terraform-arguments-settings'
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import { wrapWithReactHookForm } from '__tests__/utils/wrap-with-react-hook-form'
import { type ReactNode } from 'react'
import * as servicesDomain from '@qovery/domains/services/feature'
import { terraformFactoryMock } from '@qovery/shared/factories'
import { renderWithProviders, screen } from '@qovery/shared/util-tests'
import { PageSettingsTerraformArgumentsFeature } from './page-settings-terraform-arguments-feature'
import { TerraformArgumentsSettings } from './terraform-arguments-settings'

const useServiceSpy = jest.spyOn(servicesDomain, 'useService') as jest.Mock
const useEditServiceSpy = jest.spyOn(servicesDomain, 'useEditService') as jest.Mock

jest.mock('@tanstack/react-router', () => ({
...jest.requireActual('@tanstack/react-router'),
useParams: () => ({ organizationId: '1' }),
useNavigate: () => jest.fn(),
useLocation: () => ({ pathname: '/', search: '' }),
useRouter: () => ({
buildLocation: () => ({ href: '/' }),
}),
Link: ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) => (
<a {...props} href={`${props.to}`}>
{children}
</a>
),
}))

jest.mock('@qovery/domains/variables/feature', () => ({
...jest.requireActual('@qovery/domains/variables/feature'),
DropdownVariable: ({ children, onChange }: { children: React.ReactNode; onChange: (value: string) => void }) => (
Expand Down Expand Up @@ -41,12 +57,12 @@ describe('PageSettingsTerraformArgumentsFeature', () => {
})

it('should render', () => {
const { baseElement } = renderWithProviders(wrapWithReactHookForm(<PageSettingsTerraformArgumentsFeature />))
const { baseElement } = renderWithProviders(wrapWithReactHookForm(<TerraformArgumentsSettings />))
expect(baseElement).toBeTruthy()
})

it('should correctly submit the form', async () => {
const { userEvent } = renderWithProviders(wrapWithReactHookForm(<PageSettingsTerraformArgumentsFeature />))
const { userEvent } = renderWithProviders(wrapWithReactHookForm(<TerraformArgumentsSettings />))
const submitButton = screen.getByText('Save')
const inputLabelForApply = screen.getByText('Arguments for apply')
const inputLabelForDestroy = screen.getByText('Arguments for destroy')
Expand Down Expand Up @@ -89,15 +105,15 @@ describe('PageSettingsTerraformArgumentsFeature', () => {
})

it('should render variable interpolation wand button for each command', () => {
renderWithProviders(wrapWithReactHookForm(<PageSettingsTerraformArgumentsFeature />))
renderWithProviders(wrapWithReactHookForm(<TerraformArgumentsSettings />))

// Check that the wand button appears for each terraform command
const wandButtons = screen.getAllByTestId('dropdown-variable')
expect(wandButtons).toHaveLength(5) // init, validate, plan, apply, destroy
})

it('should add variable interpolation to empty input when wand is clicked', async () => {
const { userEvent } = renderWithProviders(wrapWithReactHookForm(<PageSettingsTerraformArgumentsFeature />))
const { userEvent } = renderWithProviders(wrapWithReactHookForm(<TerraformArgumentsSettings />))

const inputLabelForPlan = screen.getByText('Arguments for plan')
const inputForPlan = inputLabelForPlan.closest('div')?.querySelector('input')
Expand All @@ -114,7 +130,7 @@ describe('PageSettingsTerraformArgumentsFeature', () => {
})

it('should append variable interpolation to existing input when wand is clicked', async () => {
const { userEvent } = renderWithProviders(wrapWithReactHookForm(<PageSettingsTerraformArgumentsFeature />))
const { userEvent } = renderWithProviders(wrapWithReactHookForm(<TerraformArgumentsSettings />))

// Get the input for init command (which has default value '-auto-approve')
const inputLabelForInit = screen.getByText('Arguments for init')
Expand All @@ -133,7 +149,7 @@ describe('PageSettingsTerraformArgumentsFeature', () => {
})

it('should correctly submit form with variable interpolation', async () => {
const { userEvent } = renderWithProviders(wrapWithReactHookForm(<PageSettingsTerraformArgumentsFeature />))
const { userEvent } = renderWithProviders(wrapWithReactHookForm(<TerraformArgumentsSettings />))

// Get the apply input and add a variable using the wand
const inputLabelForApply = screen.getByText('Arguments for apply')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useParams } from '@tanstack/react-router'
import { Controller, FormProvider, useForm } from 'react-hook-form'
import { useParams } from 'react-router-dom'
import { type Terraform } from '@qovery/domains/services/data-access'
import { useEditService, useService } from '@qovery/domains/services/feature'
import { DropdownVariable } from '@qovery/domains/variables/feature'
import { NeedHelp } from '@qovery/shared/assistant/feature'
import { SettingsHeading } from '@qovery/shared/console-shared'
import { Button, Heading, Icon, InputText, Section } from '@qovery/shared/ui'
import { twMerge } from '@qovery/shared/util-js'
import { buildEditServicePayload } from '@qovery/shared/util-services'
Expand All @@ -15,8 +16,10 @@ const commands = [
hint: (
<div>
Example:{' '}
<code className="rounded bg-neutral-150 px-1 py-0.5 text-2xs text-neutral-350">-lock=false -upgrade</code>.
Arguments are separated by a space.
<code className="rounded bg-surface-neutral-component px-1 py-0.5 text-2xs text-neutral-subtle">
-lock=false -upgrade
</code>
. Arguments are separated by a space.
</div>
),
},
Expand All @@ -25,9 +28,10 @@ const commands = [
description: 'Check whether the configuration is valid.',
hint: (
<div>
Example: <code className="rounded bg-neutral-150 px-1 py-0.5 text-2xs text-neutral-350">-json</code> or{' '}
<code className="rounded bg-neutral-150 px-1 py-0.5 text-2xs text-neutral-350">-no-color</code>. Arguments are
separated by a space.
Example:{' '}
<code className="rounded bg-surface-neutral-component px-1 py-0.5 text-2xs text-neutral-subtle">-json</code> or{' '}
<code className="rounded bg-surface-neutral-component px-1 py-0.5 text-2xs text-neutral-subtle">-no-color</code>
. Arguments are separated by a space.
</div>
),
},
Expand All @@ -36,8 +40,11 @@ const commands = [
description: 'Show changes required by the current configuration.',
hint: (
<div>
Example: <code className="rounded bg-neutral-150 px-1 py-0.5 text-2xs text-neutral-350">-refresh=false</code>.
Arguments are separated by a space.
Example:{' '}
<code className="rounded bg-surface-neutral-component px-1 py-0.5 text-2xs text-neutral-subtle">
-refresh=false
</code>
. Arguments are separated by a space.
</div>
),
},
Expand All @@ -46,8 +53,11 @@ const commands = [
description: 'Create or update infrastructure.',
hint: (
<div>
Example: <code className="rounded bg-neutral-150 px-1 py-0.5 text-2xs text-neutral-350">-auto-approve</code>.
Arguments are separated by a space.
Example:{' '}
<code className="rounded bg-surface-neutral-component px-1 py-0.5 text-2xs text-neutral-subtle">
-auto-approve
</code>
. Arguments are separated by a space.
</div>
),
},
Expand All @@ -56,16 +66,16 @@ const commands = [
description: 'Destroy previously-created infrastructure.',
hint: (
<div>
Example: <code className="rounded bg-neutral-150 px-1 py-0.5 text-2xs text-neutral-350">-target</code>.
Example:{' '}
<code className="rounded bg-surface-neutral-component px-1 py-0.5 text-2xs text-neutral-subtle">-target</code>.
Arguments are separated by a space.
</div>
),
},
]

export function PageSettingsTerraformArgumentsFeature() {
const { organizationId = '', projectId = '', environmentId = '', applicationId = '' } = useParams()
const { data: service } = useService({ serviceId: applicationId })
const TerraformArgumentsSettingsContent = ({ service }: { service: Terraform }) => {
const { organizationId = '', projectId = '', environmentId = '' } = useParams({ strict: false })
const { mutate: editService, isLoading: isLoadingEditService } = useEditService({
organizationId,
projectId,
Expand All @@ -85,50 +95,46 @@ export function PageSettingsTerraformArgumentsFeature() {
})

const onSubmit = methods.handleSubmit((data) => {
if (!service || !data) return
if (!data) return

if (service.serviceType === 'TERRAFORM') {
const payload = buildEditServicePayload({
service,
request: {
action_extra_arguments: {
init: data['init'] ?? [],
validate: data['validate'] ?? [],
plan: data['plan'] ?? [],
apply: data['apply'] ?? [],
destroy: data['destroy'] ?? [],
},
const payload = buildEditServicePayload({
service,
request: {
action_extra_arguments: {
init: data['init'] ?? [],
validate: data['validate'] ?? [],
plan: data['plan'] ?? [],
apply: data['apply'] ?? [],
destroy: data['destroy'] ?? [],
},
})
},
})

editService({
serviceId: service.id,
payload,
})
}
editService({
serviceId: service.id,
payload,
})
})

return (
<div className="flex w-full max-w-content-with-navigation-left flex-col p-8">
<FormProvider {...methods}>
<div className="space-y-10">
<Section className="space-y-2">
<Heading level={1}>Terraform arguments</Heading>
<p className="text-sm text-neutral-350">Configure the arguments passed to each Terraform command.</p>
<NeedHelp />
</Section>

<FormProvider {...methods}>
<Section className="px-8 pb-8 pt-6">
<SettingsHeading
title="Terraform arguments"
description="Configure the arguments passed to each Terraform command."
/>
<div className="max-w-content-with-navigation-left space-y-10">
<Section className="gap-4">
<div className="space-y-1">
<Heading level={2}>Commands</Heading>
<p className="text-sm text-neutral-350">Specify additional arguments for each Terraform command.</p>
<p className="text-sm text-neutral-subtle">Specify additional arguments for each Terraform command.</p>
</div>

{commands.map((command) => (
<div key={command.name} className="space-y-4 rounded border border-neutral-250 bg-neutral-100 p-4">
<div key={command.name} className="space-y-4 rounded-lg border border-neutral bg-surface-neutral p-4">
<div className="space-y-1">
<p className="text-sm font-medium capitalize text-neutral-400">{command.name}</p>
<p className="text-sm text-neutral-350">{command.description}</p>
<p className="text-sm font-medium capitalize text-neutral">{command.name}</p>
<p className="text-sm text-neutral-subtle">{command.description}</p>
</div>
<Controller
name={`${command.name}`}
Expand All @@ -151,7 +157,7 @@ export function PageSettingsTerraformArgumentsFeature() {
>
<button
className={twMerge(
'flex items-center justify-center border-none bg-transparent px-1 text-neutral-350 hover:text-neutral-400'
'flex items-center justify-center border-none bg-transparent px-1 text-neutral-subtle hover:text-neutral'
)}
type="button"
>
Expand All @@ -178,7 +184,16 @@ export function PageSettingsTerraformArgumentsFeature() {
</Button>
</div>
</div>
</FormProvider>
</div>
</Section>
</FormProvider>
)
}

export function TerraformArgumentsSettings() {
const { serviceId = '' } = useParams({ strict: false })
const { data: service } = useService({ serviceId, suspense: true })

if (service?.serviceType !== 'TERRAFORM') return null

return <TerraformArgumentsSettingsContent service={service} />
}
Loading