Skip to content

Commit

Permalink
feat(jobs): cron and lifecycle job creation (#393)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdebon committed Dec 22, 2022
1 parent 1464508 commit 74eb8c6
Show file tree
Hide file tree
Showing 116 changed files with 3,869 additions and 306 deletions.
5 changes: 0 additions & 5 deletions __tests__/utils/providers.tsx
@@ -1,6 +1,5 @@
import { Auth0Provider } from '@auth0/auth0-react'
import { configureStore } from '@reduxjs/toolkit'
import posthog from 'posthog-js'
import React, { ComponentType, ReactNode } from 'react'
import { Provider } from 'react-redux'
import { MemoryRouter } from 'react-router-dom'
Expand All @@ -21,10 +20,6 @@ export type Props = {
export const Wrapper: React.FC<Props> = ({ children, reduxState = initialRootState(), route = '/' }) => {
window.history.pushState({}, 'Test page', route)

posthog.init('__test__posthog__token', {
api_host: '__test__environment__posthog__apihost',
})

const store = configureStore({
reducer: rootReducer,
preloadedState: reduxState,
Expand Down
7 changes: 5 additions & 2 deletions __tests__/utils/wrap-with-react-hook-form.tsx
@@ -1,5 +1,5 @@
import { FormProvider, useForm } from 'react-hook-form'
import React from 'react'
import { FormProvider, useForm } from 'react-hook-form'

/**
* Testing Library utility function to wrap tested component in React Hook Form
Expand All @@ -8,7 +8,10 @@ import React from 'react'
* @param {Object} objectParameters.defaultValues Initial form values to pass into
* React Hook Form, which you can then assert against
*/
export function wrapWithReactHookForm(ui: React.ReactElement, { defaultValues = {} } = {}) {
export function wrapWithReactHookForm<T extends { [x: string]: any } | undefined>(
ui: React.ReactElement,
{ defaultValues }: { defaultValues?: T } = {}
) {
const Wrapper = ({ children }: { children: React.ReactElement }) => {
const methods = useForm({ defaultValues, mode: 'all' })
return <FormProvider {...methods}>{children}</FormProvider>
Expand Down
1 change: 1 addition & 0 deletions libs/domains/application/src/index.ts
@@ -1,5 +1,6 @@
export * from './lib/slices/custom-domain.slice'
export * from './lib/mocks/factories/application-factory.mock'
export * from './lib/mocks/factories/job-factory.mock'
export * from './lib/mocks/factories/application-deployment-factory.mock'
export * from './lib/slices/applications.slice'
export * from './lib/slices/application.actions'
145 changes: 145 additions & 0 deletions libs/domains/application/src/lib/mocks/factories/job-factory.mock.ts
@@ -0,0 +1,145 @@
import { Chance } from 'chance'
import { GitProviderEnum, StorageTypeEnum } from 'qovery-typescript-axios'
import { JobApplicationEntity } from '@qovery/shared/interfaces'

const chance = new Chance()

export const cronjobFactoryMock = (howMany: number, withContainer: boolean = false): JobApplicationEntity[] =>
Array.from({ length: howMany }).map((_, index) => {
let source
if (!withContainer) {
source = {
docker: {
git_repository: {
has_access: true,
deployed_commit_id: '8a21ddb195d821781d46eb1d8f26d5ae13609dd1',
deployed_commit_date: '2022-12-19T11:57:07.425715Z',
deployed_commit_contributor: 'TAGS_NOT_IMPLEMENTED',
deployed_commit_tag: 'TAGS_NOT_IMPLEMENTED',
provider: GitProviderEnum.GITHUB,
owner: 'bdebon',
url: 'https://github.com/Qovery/admin-ui.git',
name: 'Qovery/admin-ui',
branch: 'develop',
root_path: '/',
},
dockerfile_path: 'Dockerfile',
},
}
} else {
source = {
image: {
name: 'nginx',
image_name: 'nginx/nginx',
registry_id: chance.guid(),
},
}
}

return {
id: `${index}`,
created_at: new Date().toString(),
updated_at: new Date().toString(),
storage: [
{
id: chance.guid(),
type: chance.pickone(Object.values([StorageTypeEnum.FAST_SSD])),
size: 10,
mount_point: '',
},
],
environment: {
id: chance.guid(),
},
maximum_cpu: 10,
maximum_memory: 10,
name: chance.name(),
description: chance.word({ length: 10 }),
max_duration_seconds: 10,
max_nb_restart: 10,
port: 80,
cpu: 1000,
memory: 1024,
auto_preview: false,
source,
schedule: {
cronjob: {
scheduled_at: '0 0 * * *',
entrypoint: '/',
arguments: [],
},
},
registry: {
id: chance.guid(),
},
}
})

export const lifecycleJobFactoryMock = (howMany: number, withContainer: boolean = false): JobApplicationEntity[] =>
Array.from({ length: howMany }).map((_, index) => {
let source
if (!withContainer) {
source = {
docker: {
git_repository: {
has_access: true,
deployed_commit_id: '8a21ddb195d821781d46eb1d8f26d5ae13609dd1',
deployed_commit_date: '2022-12-19T11:57:07.425715Z',
deployed_commit_contributor: 'TAGS_NOT_IMPLEMENTED',
deployed_commit_tag: 'TAGS_NOT_IMPLEMENTED',
provider: GitProviderEnum.GITHUB,
owner: 'bdebon',
url: 'https://github.com/Qovery/admin-ui.git',
name: 'Qovery/admin-ui',
branch: 'develop',
root_path: '/',
},
dockerfile_path: 'Dockerfile',
},
}
} else {
source = {
image: {
name: 'nginx',
image_name: 'nginx/nginx',
registry_id: chance.guid(),
},
}
}

return {
id: `${index}`,
created_at: new Date().toString(),
updated_at: new Date().toString(),
storage: [
{
id: chance.guid(),
type: chance.pickone(Object.values([StorageTypeEnum.FAST_SSD])),
size: 10,
mount_point: '',
},
],
environment: {
id: chance.guid(),
},
maximum_cpu: 10,
maximum_memory: 10,
name: chance.name(),
description: chance.word({ length: 10 }),
max_duration_seconds: 10,
max_nb_restart: 10,
port: 80,
cpu: 1000,
memory: 1024,
auto_preview: false,
source,
schedule: {
on_start: {
arguments: [],
},
},
registry: {
id: chance.guid(),
},
}
})
18 changes: 13 additions & 5 deletions libs/domains/application/src/lib/slices/applications.slice.ts
Expand Up @@ -29,11 +29,13 @@ import {
Instance,
JobDeploymentHistoryApi,
JobMainCallsApi,
JobRequest,
JobResponse,
JobsApi,
Link,
Status,
} from 'qovery-typescript-axios'
import { ServiceTypeEnum, isContainer, isJob } from '@qovery/shared/enums'
import { ServiceTypeEnum, isApplication, isContainer, isJob } from '@qovery/shared/enums'
import {
ApplicationEntity,
ApplicationsState,
Expand All @@ -49,6 +51,7 @@ import {
getEntitiesByIds,
refactoContainerApplicationPayload,
refactoGitApplicationPayload,
refactoJobPayload,
shortToLongId,
} from '@qovery/shared/utils'
import { RootState } from '@qovery/store'
Expand Down Expand Up @@ -130,6 +133,9 @@ export const editApplication = createAsyncThunk(
if (isContainer(payload.serviceType)) {
const cloneApplication = Object.assign({}, refactoContainerApplicationPayload(payload.data))
response = await containerMainCallsApi.editContainer(payload.applicationId, cloneApplication as ContainerRequest)
} else if (isJob(payload.serviceType)) {
const cloneJob = Object.assign({}, refactoJobPayload(payload.data as Partial<JobApplicationEntity>))
response = await jobMainCallsApi.editJob(payload.applicationId, cloneJob as JobRequest)
} else {
const cloneApplication = Object.assign({}, refactoGitApplicationPayload(payload.data))
response = await applicationMainCallsApi.editApplication(
Expand All @@ -146,17 +152,19 @@ export const createApplication = createAsyncThunk(
'application/create',
async (payload: {
environmentId: string
data: ApplicationRequest | ContainerRequest
data: ApplicationRequest | ContainerRequest | JobRequest
serviceType: ServiceTypeEnum
}) => {
let response
if (isContainer(payload.serviceType)) {
response = await containersApi.createContainer(payload.environmentId, payload.data as ContainerRequest)
} else {
} else if (isApplication(payload.serviceType)) {
response = await applicationsApi.createApplication(payload.environmentId, payload.data as ApplicationRequest)
} else {
response = await jobsApi.createJob(payload.environmentId, payload.data as JobRequest)
}

return response.data as Application | ContainerResponse
return response.data as Application | ContainerResponse | JobResponse
}
)

Expand Down Expand Up @@ -363,7 +371,7 @@ export const applicationsSlice = createSlice({
if (!action.meta.arg.silentToaster) {
toast(
ToastEnum.SUCCESS,
`Application updated`,
`${isJob(action.payload) ? 'Job' : 'Application'} updated`,
'You must redeploy to apply the settings update',
action.meta.arg.toasterCallback,
undefined,
Expand Down
Expand Up @@ -32,6 +32,8 @@ export const fetchSecretEnvironmentVariables = createAsyncThunk(
let response
if (isContainer(payload.serviceType)) {
response = await containerSecretApi.listContainerSecrets(payload.applicationId)
} else if (isJob(payload.serviceType)) {
response = await jobSecretApi.listJobSecrets(payload.applicationId)
} else {
response = await applicationSecretApi.listApplicationSecrets(payload.applicationId)
}
Expand Down
Expand Up @@ -235,7 +235,7 @@ export const organizationSlice = createSlice({
state.loadingStatus = 'loading'
})
.addCase(fetchOrganization.fulfilled, (state: OrganizationState, action: PayloadAction<OrganizationEntity[]>) => {
organizationAdapter.setAll(state, action.payload)
organizationAdapter.upsertMany(state, action.payload)
state.loadingStatus = 'loaded'
})
.addCase(fetchOrganization.rejected, (state: OrganizationState, action) => {
Expand All @@ -246,7 +246,7 @@ export const organizationSlice = createSlice({
.addCase(
fetchOrganizationById.fulfilled,
(state: OrganizationState, action: PayloadAction<OrganizationEntity>) => {
organizationAdapter.addOne(state, action.payload)
organizationAdapter.upsertOne(state, action.payload)
state.loadingStatus = 'loaded'
}
)
Expand Down
Expand Up @@ -6,9 +6,9 @@ import { getEnvironmentVariablesState } from '@qovery/domains/environment-variab
import { ServiceTypeEnum } from '@qovery/shared/enums'
import { EnvironmentVariableEntity, EnvironmentVariableSecretOrPublic } from '@qovery/shared/interfaces'
import { useModal } from '@qovery/shared/ui'
import { computeAvailableScope } from '@qovery/shared/utils'
import { AppDispatch, RootState } from '@qovery/store'
import CrudEnvironmentVariableModal from '../../ui/crud-environment-variable-modal/crud-environment-variable-modal'
import { computeAvailableScope } from '../../utils/compute-available-environment-variable-scope'
import { handleSubmitForEnvSecretCreation } from './handle-submit/handle-submit'

export interface CrudEnvironmentVariableModalFeatureProps {
Expand Down
Expand Up @@ -16,10 +16,9 @@ import {
SecretEnvironmentVariableEntity,
} from '@qovery/shared/interfaces'
import { useModal } from '@qovery/shared/ui'
import { parseEnvText } from '@qovery/shared/utils'
import { computeAvailableScope, parseEnvText } from '@qovery/shared/utils'
import { AppDispatch, RootState } from '@qovery/store'
import ImportEnvironmentVariableModal from '../../ui/import-environment-variable-modal/import-environment-variable-modal'
import { computeAvailableScope } from '../../utils/compute-available-environment-variable-scope'
import { changeScopeForAll } from './utils/change-scope-all'
import { deleteEntry } from './utils/delete-entry'
import { parsedToForm } from './utils/file-to-form'
Expand Down
@@ -1,7 +1,7 @@
import { APIVariableScopeEnum } from 'qovery-typescript-axios'
import { ServiceTypeEnum } from '@qovery/shared/enums'
import { EnvironmentVariableSecretOrPublic } from '@qovery/shared/interfaces'
import { getScopeHierarchy } from '../../../utils/compute-available-environment-variable-scope'
import { getScopeHierarchy } from '@qovery/shared/utils'

export const validateKey = (
value: string,
Expand Down

0 comments on commit 74eb8c6

Please sign in to comment.