Add pipeline log output download (#1023)

This commit is contained in:
Lauris BH 2022-07-17 22:51:10 +03:00 committed by GitHub
parent 73ee12a6c3
commit f654e1f316
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 84 additions and 8 deletions

View file

@ -178,13 +178,15 @@
"exit_code": "exit code {exitCode}", "exit_code": "exit code {exitCode}",
"loading": "Loading ...", "loading": "Loading ...",
"pipeline": "Pipeline #{buildId}", "pipeline": "Pipeline #{buildId}",
"log_download_error": "There was an error while downloading the log file",
"actions": { "actions": {
"cancel": "Cancel", "cancel": "Cancel",
"restart": "Restart", "restart": "Restart",
"canceled": "This step has been canceled.", "canceled": "This step has been canceled.",
"cancel_success": "Pipeline canceled", "cancel_success": "Pipeline canceled",
"restart_success": "Pipeline restarted" "restart_success": "Pipeline restarted",
"log_download": "Download"
}, },
"protected": { "protected": {
"awaits": "This pipeline is awaiting approval by some maintainer!", "awaits": "This pipeline is awaiting approval by some maintainer!",

View file

@ -178,13 +178,15 @@
"exit_code": "iziešanas kods {exitCode}", "exit_code": "iziešanas kods {exitCode}",
"loading": "Notiek ielāde...", "loading": "Notiek ielāde...",
"pipeline": "Konvejerdarbs #{buildId}", "pipeline": "Konvejerdarbs #{buildId}",
"log_download_error": "Veicot žurnālfaila lejupielādi notika kļūda",
"actions": { "actions": {
"cancel": "Atcelt", "cancel": "Atcelt",
"restart": "Pārstartēt", "restart": "Pārstartēt",
"canceled": "Šis solis tika atcelts.", "canceled": "Šis solis tika atcelts.",
"cancel_success": "Konvejerdarbs atcelts", "cancel_success": "Konvejerdarbs atcelts",
"restart_success": "Konvejerdarbs pārstartēts" "restart_success": "Konvejerdarbs pārstartēts",
"log_download": "Lejupielādēt"
}, },
"protected": { "protected": {
"awaits": "Šim konvejerdarbam ir nepieciešams apstiprinājums no atbildīgajām personām!", "awaits": "Šim konvejerdarbam ir nepieciešams apstiprinājums no atbildīgajām personām!",

View file

@ -28,6 +28,7 @@
color === 'red', color === 'red',
...passedClasses, ...passedClasses,
}" }"
:title="title"
:disabled="disabled" :disabled="disabled"
@click="doClick" @click="doClick"
> >
@ -69,6 +70,11 @@ export default defineComponent({
default: null, default: null,
}, },
title: {
type: String,
default: null,
},
disabled: { disabled: {
type: Boolean, type: Boolean,
required: false, required: false,

View file

@ -36,6 +36,7 @@
<i-mdi-chevron-right v-else-if="name === 'chevron-right'" class="h-6 w-6" /> <i-mdi-chevron-right v-else-if="name === 'chevron-right'" class="h-6 w-6" />
<i-carbon-close-outline v-else-if="name === 'close'" class="h-6 w-6" /> <i-carbon-close-outline v-else-if="name === 'close'" class="h-6 w-6" />
<i-ic-baseline-edit v-else-if="name === 'edit'" class="h-6 w-6" /> <i-ic-baseline-edit v-else-if="name === 'edit'" class="h-6 w-6" />
<i-ic-baseline-download v-else-if="name === 'download'" class="h-6 w-6" />
<div v-else-if="name === 'blank'" class="h-6 w-6" /> <div v-else-if="name === 'blank'" class="h-6 w-6" />
</template> </template>
@ -80,7 +81,8 @@ export type IconNames =
| 'chevron-right' | 'chevron-right'
| 'turn-off' | 'turn-off'
| 'close' | 'close'
| 'edit'; | 'edit'
| 'download';
export default defineComponent({ export default defineComponent({
name: 'Icon', name: 'Icon',

View file

@ -8,7 +8,21 @@
<Icon name="close" class="ml-auto" /> <Icon name="close" class="ml-auto" />
</div> </div>
<div class="flex flex-grow flex-col bg-gray-300 dark:bg-dark-gray-700 md:m-2 md:mt-0 md:rounded-md overflow-hidden"> <div
class="flex flex-grow flex-col bg-gray-300 dark:bg-dark-gray-700 md:m-2 md:mt-0 md:rounded-md overflow-hidden"
@mouseover="showActions = true"
@mouseleave="showActions = false"
>
<div v-show="showActions" class="absolute top-0 right-0 z-50 mt-2 mr-4 hidden md:flex">
<Button
v-if="proc?.end_time !== undefined"
:is-loading="downloadInProgress"
:title="$t('repo.build.actions.log_download')"
start-icon="download"
@click="download"
/>
</div>
<div v-show="loadedLogs" class="w-full flex-grow p-2"> <div v-show="loadedLogs" class="w-full flex-grow p-2">
<div id="terminal" class="w-full h-full" /> <div id="terminal" class="w-full h-full" />
</div> </div>
@ -47,20 +61,23 @@ import {
toRef, toRef,
watch, watch,
} from 'vue'; } from 'vue';
import { useI18n } from 'vue-i18n';
import { Terminal } from 'xterm'; import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit'; import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links'; import { WebLinksAddon } from 'xterm-addon-web-links';
import Button from '~/components/atomic/Button.vue';
import Icon from '~/components/atomic/Icon.vue'; import Icon from '~/components/atomic/Icon.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useDarkMode } from '~/compositions/useDarkMode'; import { useDarkMode } from '~/compositions/useDarkMode';
import useNotifications from '~/compositions/useNotifications';
import { Build, Repo } from '~/lib/api/types'; import { Build, Repo } from '~/lib/api/types';
import { findProc, isProcFinished, isProcRunning } from '~/utils/helpers'; import { findProc, isProcFinished, isProcRunning } from '~/utils/helpers';
export default defineComponent({ export default defineComponent({
name: 'BuildLog', name: 'BuildLog',
components: { Icon }, components: { Icon, Button },
props: { props: {
build: { build: {
@ -82,6 +99,8 @@ export default defineComponent({
}, },
setup(props) { setup(props) {
const notifications = useNotifications();
const i18n = useI18n();
const build = toRef(props, 'build'); const build = toRef(props, 'build');
const procId = toRef(props, 'procId'); const procId = toRef(props, 'procId');
const repo = inject<Ref<Repo>>('repo'); const repo = inject<Ref<Repo>>('repo');
@ -103,6 +122,41 @@ export default defineComponent({
const fitAddon = ref(new FitAddon()); const fitAddon = ref(new FitAddon());
const loadedLogs = ref(true); const loadedLogs = ref(true);
const autoScroll = ref(true); // TODO const autoScroll = ref(true); // TODO
const showActions = ref(false);
const downloadInProgress = ref(false);
async function download() {
if (!repo?.value || !build.value || !proc.value) {
throw new Error('The reposiotry, build or proc was undefined');
}
let logs;
try {
downloadInProgress.value = true;
logs = await apiClient.getLogs(repo.value.owner, repo.value.name, build.value.number, proc.value.pid);
} catch (e) {
notifications.notifyError(e, i18n.t('repo.build.log_download_error'));
return;
} finally {
downloadInProgress.value = false;
}
const fileURL = window.URL.createObjectURL(
new Blob([logs.map((line) => line.out).join('')], {
type: 'text/plain',
}),
);
const fileLink = document.createElement('a');
fileLink.href = fileURL;
fileLink.setAttribute(
'download',
`${repo.value.owner}-${repo.value.name}-${build.value.number}-${proc.value.name}.log`,
);
document.body.appendChild(fileLink);
fileLink.click();
document.body.removeChild(fileLink);
window.URL.revokeObjectURL(fileURL);
}
async function loadLogs() { async function loadLogs() {
if (loadedProcSlug.value === procSlug.value) { if (loadedProcSlug.value === procSlug.value) {
@ -231,7 +285,7 @@ export default defineComponent({
window.removeEventListener('resize', resize); window.removeEventListener('resize', resize);
}); });
return { proc, loadedLogs }; return { proc, loadedLogs, showActions, download, downloadInProgress };
}, },
}); });
</script> </script>

View file

@ -1,5 +1,15 @@
import Notifications, { notify } from '@kyvg/vue3-notification'; import Notifications, { NotificationsOptions, notify } from '@kyvg/vue3-notification';
export const notifications = Notifications; export const notifications = Notifications;
export default () => ({ notify }); function notifyError(err: unknown, args: NotificationsOptions | string = {}): void {
// eslint-disable-next-line no-console
console.error(err);
const mArgs = typeof args === 'string' ? { title: args } : args;
const title = mArgs?.title || (err as Error)?.message || `${err}`;
notify({ type: 'error', ...mArgs, title });
}
export default () => ({ notify, notifyError });