Add button to trigger deployments (#1415)

Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: Anbraten <anton@ju60.de>
Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
smainz 2022-12-01 21:41:36 +01:00 committed by GitHub
parent 5d1ec770c9
commit cfb288201f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 168 additions and 1 deletions

1
web/components.d.ts vendored
View file

@ -15,6 +15,7 @@ declare module '@vue/runtime-core' {
Checkbox: typeof import('./src/components/form/Checkbox.vue')['default']
CheckboxesField: typeof import('./src/components/form/CheckboxesField.vue')['default']
CronTab: typeof import('./src/components/repo/settings/CronTab.vue')['default']
DeployPipelinePopup: typeof import('./src/components/layout/popups/DeployPipelinePopup.vue')['default']
DocsLink: typeof import('./src/components/atomic/DocsLink.vue')['default']
FluidContainer: typeof import('./src/components/layout/FluidContainer.vue')['default']
GeneralTab: typeof import('./src/components/repo/settings/GeneralTab.vue')['default']

View file

@ -41,6 +41,18 @@
"value": "Variable value"
}
},
"deploy_pipeline": {
"title": "Trigger deployment event for current pipeline #{pipelineId}",
"enter_target": "Target deployment environment",
"trigger": "Deploy",
"variables": {
"add": "Add variable",
"title": "Additional pipeline variables",
"desc": "Specify additional variables to use in your pipeline. Variables with the same name will be overwritten.",
"name": "Variable name",
"value": "Variable value"
}
},
"activity": "Activity",
"branches": "Branches",
"add": "Add repository",
@ -218,6 +230,7 @@
"restart": "Restart",
"canceled": "This step has been canceled.",
"cancel_success": "Pipeline canceled",
"deploy": "Deploy",
"restart_success": "Pipeline restarted",
"log_download": "Download",
"log_auto_scroll": "Automatically scroll down",

View file

@ -0,0 +1,120 @@
<template>
<Popup :open="open" @close="$emit('close')">
<Panel v-if="!loading">
<form @submit.prevent="triggerDeployPipeline">
<span class="text-xl text-color">{{ $t('repo.deploy_pipeline.title', { pipelineId: pipelineNumber }) }}</span>
<InputField :label="$t('repo.deploy_pipeline.enter_target')">
<TextField v-model="payload.environment" required />
</InputField>
<InputField :label="$t('repo.deploy_pipeline.variables.title')">
<span class="text-sm text-color-alt mb-2">{{ $t('repo.deploy_pipeline.variables.desc') }}</span>
<div class="flex flex-col gap-2">
<div v-for="(value, name) in payload.variables" :key="name" class="flex gap-4">
<TextField :model-value="name" disabled />
<TextField :model-value="value" disabled />
<div class="w-34 flex-shrink-0">
<Button color="red" class="ml-auto" @click="deleteVar(name)">
<i-la-times />
</Button>
</div>
</div>
<form class="flex gap-4" @submit.prevent="addPipelineVariable">
<TextField
v-model="newPipelineVariable.name"
:placeholder="$t('repo.deploy_pipeline.variables.name')"
required
/>
<TextField
v-model="newPipelineVariable.value"
:placeholder="$t('repo.deploy_pipeline.variables.value')"
required
/>
<Button
class="w-34 flex-shrink-0"
start-icon="plus"
type="submit"
:text="$t('repo.deploy_pipeline.variables.add')"
/>
</form>
</div>
</InputField>
<Button type="submit" :text="$t('repo.deploy_pipeline.trigger')" />
</form>
</Panel>
</Popup>
</template>
<script lang="ts" setup>
import { onMounted, ref, toRef } from 'vue';
import { useRouter } from 'vue-router';
import Button from '~/components/atomic/Button.vue';
import InputField from '~/components/form/InputField.vue';
import TextField from '~/components/form/TextField.vue';
import Panel from '~/components/layout/Panel.vue';
import Popup from '~/components/layout/Popup.vue';
import useApiClient from '~/compositions/useApiClient';
import { inject } from '~/compositions/useInjectProvide';
const props = defineProps<{
open: boolean;
pipelineNumber: number;
}>();
const emit = defineEmits<{
(event: 'close'): void;
}>();
const apiClient = useApiClient();
const repo = inject('repo');
const router = useRouter();
const payload = ref<{ id: string; environment: string; variables: Record<string, string> }>({
id: '',
environment: '',
variables: {},
});
const newPipelineVariable = ref<{ name: string; value: string }>({ name: '', value: '' });
const loading = ref(true);
onMounted(async () => {
loading.value = false;
});
function addPipelineVariable() {
if (!newPipelineVariable.value.name || !newPipelineVariable.value.value) {
return;
}
payload.value.variables[newPipelineVariable.value.name] = newPipelineVariable.value.value;
newPipelineVariable.value.name = '';
newPipelineVariable.value.value = '';
}
function deleteVar(key: string) {
delete payload.value.variables[key];
}
const pipelineNumber = toRef(props, 'pipelineNumber');
async function triggerDeployPipeline() {
loading.value = true;
const newPipeline = await apiClient.deployPipeline(
repo.value.owner,
repo.value.name,
pipelineNumber.value,
payload.value,
);
emit('close');
await router.push({
name: 'repo-pipeline',
params: {
pipelineId: newPipeline.number,
},
});
loading.value = false;
}
</script>

View file

@ -23,6 +23,13 @@ type PipelineOptions = {
branch: string;
variables: Record<string, string>;
};
type DeploymentOptions = {
id: string;
environment: string;
variables: Record<string, string>;
};
export default class WoodpeckerClient extends ApiClient {
getRepoList(opts?: RepoListOptions): Promise<Repo[]> {
const query = encodeQueryString(opts);
@ -62,6 +69,18 @@ export default class WoodpeckerClient extends ApiClient {
return this._post(`/api/repos/${owner}/${repo}/pipelines`, options) as Promise<Pipeline>;
}
// Deploy triggers a deployment for an existing pipeline using the
// specified target environment.
deployPipeline(owner: string, repo: string, number: number, options: DeploymentOptions): Promise<Pipeline> {
const vars = {
...options.variables,
event: 'deployment',
deploy_to: options.environment,
};
const query = encodeQueryString(vars);
return this._post(`/api/repos/${owner}/${repo}/pipelines/${number}?${query}`) as Promise<Pipeline>;
}
getPipelineList(owner: string, repo: string, opts?: Record<string, string | number | boolean>): Promise<Pipeline[]> {
const query = encodeQueryString(opts);
return this._get(`/api/repos/${owner}/${repo}/pipelines?${query}`) as Promise<Pipeline[]>;

View file

@ -31,6 +31,17 @@
:is-loading="isRestartingPipeline"
@click="restartPipeline"
/>
<Button
v-if="pipeline.status === 'success'"
class="flex-shrink-0"
:text="$t('repo.pipeline.actions.deploy')"
@click="showDeployPipelinePopup = true"
/>
<DeployPipelinePopup
:pipeline-number="pipelineId"
:open="showDeployPipelinePopup"
@close="showDeployPipelinePopup = false"
/>
</template>
</template>
@ -66,7 +77,7 @@
<script lang="ts">
import { Tooltip } from 'floating-vue';
import { computed, defineComponent, inject, onBeforeUnmount, onMounted, provide, Ref, toRef, watch } from 'vue';
import { computed, defineComponent, inject, onBeforeUnmount, onMounted, provide, Ref, ref, toRef, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
@ -135,6 +146,8 @@ export default defineComponent({
const { message } = usePipeline(pipeline);
const showDeployPipelinePopup = ref(false);
async function loadPipeline(): Promise<void> {
if (!repo) {
throw new Error('Unexpected: Repo is undefined');
@ -216,6 +229,7 @@ export default defineComponent({
message,
isCancelingPipeline,
isRestartingPipeline,
showDeployPipelinePopup,
activeTab,
since,
duration,