diff --git a/openapi/v1.yaml b/openapi/v1.yaml index d75573aa..7ec240d9 100644 --- a/openapi/v1.yaml +++ b/openapi/v1.yaml @@ -1840,6 +1840,8 @@ components: type: string payload: type: string + dynamic_payload: + $ref: '#/components/schemas/DynamicPayload' production_environment: type: boolean review: @@ -1861,6 +1863,39 @@ components: - auto_merge - payload - production_environment + DynamicPayload: + type: object + properties: + enabled: + type: boolean + inputs: + # Dictionaries + type: object + additionalProperties: + type: object + properties: + type: + type: string + enum: + - select + - number + - string + - boolean + required: + type: boolean + default: + anyOf: + - type: number + - type: string + - type: boolean + description: + type: string + options: + type: array + items: + type: string + required: + - type Reviews: type: array items: diff --git a/ui/src/apis/config.ts b/ui/src/apis/config.ts index 6510a166..b8937c49 100644 --- a/ui/src/apis/config.ts +++ b/ui/src/apis/config.ts @@ -11,6 +11,10 @@ interface ConfigData { interface EnvData { name: string required_contexts?: string[] + dynamic_payload?: { + enabled: boolean, + inputs: any, + } review?: { enabled: boolean reviewers: string[] @@ -19,11 +23,15 @@ interface EnvData { const mapDataToConfig = (data: ConfigData): Config => { const envs: Env[] = data.envs.map((e: EnvData) => { - const { review } = e + const { dynamic_payload, review } = e return { name: e.name, requiredContexts: e.required_contexts, + dynamicPayload: (dynamic_payload)? { + enabled: dynamic_payload?.enabled, + inputs: dynamic_payload?.inputs, + } : undefined, review, } }) diff --git a/ui/src/apis/deployment.ts b/ui/src/apis/deployment.ts index adc15a0a..58da41ea 100644 --- a/ui/src/apis/deployment.ts +++ b/ui/src/apis/deployment.ts @@ -191,11 +191,13 @@ export const getDeployment = async (namespace: string, name: string, number: num return deployment } -export const createDeployment = async (namespace: string, name: string, type: DeploymentType = DeploymentType.Commit, ref: string, env: string): Promise => { +// eslint-disable-next-line +export const createDeployment = async (namespace: string, name: string, type: DeploymentType = DeploymentType.Commit, ref: string, env: string, payload?: any): Promise => { const body = JSON.stringify({ type, ref, - env + env, + dynamic_payload: payload }) const response = await _fetch(`${instance}/api/v1/repos/${namespace}/${name}/deployments`, { headers, diff --git a/ui/src/components/DeployForm.tsx b/ui/src/components/DeployForm.tsx index 47eecdbf..ce162ab5 100644 --- a/ui/src/components/DeployForm.tsx +++ b/ui/src/components/DeployForm.tsx @@ -182,7 +182,8 @@ export default function DeployForm(props: DeployFormProps): JSX.Element { return (
+ name="deploy" + > { + form.validateFields() + .then(values => { + props.onClickOk(values) + }) + .catch(info => { + console.log(info) + }) + } + + const onClickCancel = () => { + props.onClickCancel() + } + + // Build items dynamically + const items = new Array() + if (props.env.dynamicPayload) { + // Object.entries(props.env.dynamicPayload.inputs).forEach() + Object.entries(props.env.dynamicPayload.inputs).forEach((entry) => { + const [name, input] = entry + items.push() + }) + } + + // Build the initialValues + const initialValues: any = {} + if (props.env.dynamicPayload) { + Object.entries(props.env.dynamicPayload.inputs).forEach((entry) => { + const [name, input] = entry + initialValues[name] = input.default + }) + } + + return ( + + + {items.map(item => item)} + + + ) +} + +interface DynamicItemProps { + name: string + input: DynamicPayloadInput +} + +function DynamicItem({name, input}: DynamicItemProps): JSX.Element { + // Capitalize the first character. + const label = name.charAt(0).toUpperCase() + name.slice(1) + const description = input.description + const rules = (input.required)? [{required: true}] : [] + + switch (input.type) { + case DynamicPayloadInputTypeEnum.Select: + return ( + + + + ) + case DynamicPayloadInputTypeEnum.String: + return ( + + + + ) + case DynamicPayloadInputTypeEnum.Number: + return ( + + + + ) + case DynamicPayloadInputTypeEnum.Boolean: + return ( + + + + ) + } +} diff --git a/ui/src/models/Config.ts b/ui/src/models/Config.ts index 5744d132..519f5fbb 100644 --- a/ui/src/models/Config.ts +++ b/ui/src/models/Config.ts @@ -5,8 +5,31 @@ export default interface Config { export interface Env { name: string requiredContexts?: string[] + dynamicPayload?: DynamicPayload review?: { enabled: boolean reviewers: string[] } } + +export interface DynamicPayload { + enabled: boolean + inputs: { + [key: string]: DynamicPayloadInput + } +} + +export interface DynamicPayloadInput { + type: DynamicPayloadInputTypeEnum + required?: boolean + default?: any + description?: string + options?: string[] +} + +export enum DynamicPayloadInputTypeEnum { + Select = "select", + String = "string", + Number = "number", + Boolean = "boolean" +} diff --git a/ui/src/models/index.ts b/ui/src/models/index.ts index ae81c1f5..fd03431e 100644 --- a/ui/src/models/index.ts +++ b/ui/src/models/index.ts @@ -5,7 +5,12 @@ import Deployment, { DeploymentType, DeploymentStatus, } from "./Deployment" -import Config, { Env } from "./Config" +import Config, { + Env, + DynamicPayload, + DynamicPayloadInput, + DynamicPayloadInputTypeEnum +} from "./Config" import Commit, { Author, Status, StatusState } from "./Commit" import Branch from "./Branch" import Tag from "./Tag" @@ -33,6 +38,8 @@ export type { DeploymentStatus, Config, Env, + DynamicPayload, + DynamicPayloadInput, Commit, Author, Status, @@ -58,6 +65,7 @@ export { HttpUnprocessableEntityError, DeploymentStatusEnum, DeploymentType, + DynamicPayloadInputTypeEnum, StatusState, RequestStatus, ReviewStatusEnum, diff --git a/ui/src/redux/repoDeploy.tsx b/ui/src/redux/repoDeploy.tsx index d0d78c43..164021f1 100644 --- a/ui/src/redux/repoDeploy.tsx +++ b/ui/src/redux/repoDeploy.tsx @@ -256,9 +256,9 @@ export const fetchUser = createAsyncThunk ( +export const deploy = createAsyncThunk ( "repoDeploy/deploy", - async (_ , { getState, rejectWithValue, requestId }) => { + async (payload, { getState, rejectWithValue, requestId }) => { const { namespace, name, env, type, branch, commit, tag, deploying, deployId } = getState().repoDeploy if (!env) { throw new Error("The env is undefined.") @@ -271,11 +271,11 @@ export const deploy = createAsyncThunk state.repoDeploy, shallowEqual) const dispatch = useAppDispatch() + const [payloadModalVisible, setPayloadModalVisible] = useState(false); + useEffect(() => { const f = async () => { await dispatch(actions.init({namespace, name})) @@ -99,10 +104,20 @@ export default function RepoDeploy(): JSX.Element { } const onClickDeploy = () => { - const f = async () => { - await dispatch(deploy()) + if (env?.dynamicPayload?.enabled) { + setPayloadModalVisible(true) + } else { + dispatch(deploy(null)) } - f() + } + + const onClickDeployWithPayload = (values: any) => { + dispatch(deploy(values)) + setPayloadModalVisible(false) + } + + const onClickCancel = () => { + setPayloadModalVisible(false) } if (!display) { @@ -154,6 +169,16 @@ export default function RepoDeploy(): JSX.Element { deploying={deploying === RequestStatus.Pending} onClickDeploy={onClickDeploy} /> + {(env)? + + : + <> + } )