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}",
"loading": "Loading ...",
"pipeline": "Pipeline #{buildId}",
"log_download_error": "There was an error while downloading the log file",
"actions": {
"cancel": "Cancel",
"restart": "Restart",
"canceled": "This step has been canceled.",
"cancel_success": "Pipeline canceled",
"restart_success": "Pipeline restarted"
"restart_success": "Pipeline restarted",
"log_download": "Download"
},
"protected": {
"awaits": "This pipeline is awaiting approval by some maintainer!",

View file

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

View file

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

View file

@ -36,6 +36,7 @@
<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-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" />
</template>
@ -80,7 +81,8 @@ export type IconNames =
| 'chevron-right'
| 'turn-off'
| 'close'
| 'edit';
| 'edit'
| 'download';
export default defineComponent({
name: 'Icon',

View file

@ -8,7 +8,21 @@
<Icon name="close" class="ml-auto" />
</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 id="terminal" class="w-full h-full" />
</div>
@ -47,20 +61,23 @@ import {
toRef,
watch,
} from 'vue';
import { useI18n } from 'vue-i18n';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links';
import Button from '~/components/atomic/Button.vue';
import Icon from '~/components/atomic/Icon.vue';
import useApiClient from '~/compositions/useApiClient';
import { useDarkMode } from '~/compositions/useDarkMode';
import useNotifications from '~/compositions/useNotifications';
import { Build, Repo } from '~/lib/api/types';
import { findProc, isProcFinished, isProcRunning } from '~/utils/helpers';
export default defineComponent({
name: 'BuildLog',
components: { Icon },
components: { Icon, Button },
props: {
build: {
@ -82,6 +99,8 @@ export default defineComponent({
},
setup(props) {
const notifications = useNotifications();
const i18n = useI18n();
const build = toRef(props, 'build');
const procId = toRef(props, 'procId');
const repo = inject<Ref<Repo>>('repo');
@ -103,6 +122,41 @@ export default defineComponent({
const fitAddon = ref(new FitAddon());
const loadedLogs = ref(true);
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() {
if (loadedProcSlug.value === procSlug.value) {
@ -231,7 +285,7 @@ export default defineComponent({
window.removeEventListener('resize', resize);
});
return { proc, loadedLogs };
return { proc, loadedLogs, showActions, download, downloadInProgress };
},
});
</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 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 });