gitlab-org--gitlab-foss/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue

271 lines
7.9 KiB
Vue

<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { fetchPolicies } from '~/lib/graphql';
import httpStatusCodes from '~/lib/utils/http_status';
import { s__ } from '~/locale';
import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils';
import ConfirmUnsavedChangesDialog from './components/ui/confirm_unsaved_changes_dialog.vue';
import PipelineEditorEmptyState from './components/ui/pipeline_editor_empty_state.vue';
import PipelineEditorMessages from './components/ui/pipeline_editor_messages.vue';
import {
EDITOR_APP_STATUS_EMPTY,
EDITOR_APP_STATUS_ERROR,
EDITOR_APP_STATUS_LOADING,
LOAD_FAILURE_UNKNOWN,
} from './constants';
import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.graphql';
import getAppStatus from './graphql/queries/client/app_status.graphql';
import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
import getIsNewCiConfigFile from './graphql/queries/client/is_new_ci_config_file.graphql';
import PipelineEditorHome from './pipeline_editor_home.vue';
export default {
components: {
ConfirmUnsavedChangesDialog,
GlLoadingIcon,
PipelineEditorEmptyState,
PipelineEditorHome,
PipelineEditorMessages,
},
inject: {
ciConfigPath: {
default: '',
},
projectFullPath: {
default: '',
},
},
data() {
return {
ciConfigData: {},
failureType: null,
failureReasons: [],
initialCiFileContent: '',
isNewCiConfigFile: false,
lastCommittedContent: '',
currentCiFileContent: '',
successType: null,
showStartScreen: false,
showSuccess: false,
showFailure: false,
};
},
apollo: {
initialCiFileContent: {
fetchPolicy: fetchPolicies.NETWORK,
query: getBlobContent,
// If it's a brand new file, we don't want to fetch the content.
// Then when the user commits the first time, the query would run
// to get the initial file content, but we already have it in `lastCommitedContent`
// so we skip the loading altogether.
skip({ isNewCiConfigFile, lastCommittedContent }) {
return isNewCiConfigFile || lastCommittedContent;
},
variables() {
return {
projectPath: this.projectFullPath,
path: this.ciConfigPath,
ref: this.currentBranch,
};
},
update(data) {
return data?.blobContent?.rawData;
},
result({ data }) {
const fileContent = data?.blobContent?.rawData ?? '';
this.lastCommittedContent = fileContent;
this.currentCiFileContent = fileContent;
// make sure to reset the start screen flag during a refetch
// e.g. when switching branches
if (fileContent.length) {
this.showStartScreen = false;
}
},
error(error) {
this.handleBlobContentError(error);
},
watchLoading(isLoading) {
if (isLoading) {
this.setAppStatus(EDITOR_APP_STATUS_LOADING);
}
},
},
ciConfigData: {
query: getCiConfigData,
skip({ currentCiFileContent }) {
return !currentCiFileContent;
},
variables() {
return {
projectPath: this.projectFullPath,
content: this.currentCiFileContent,
};
},
update(data) {
const { ciConfig } = data || {};
const stageNodes = ciConfig?.stages?.nodes || [];
const stages = unwrapStagesWithNeeds(stageNodes);
return { ...ciConfig, stages };
},
result({ data }) {
this.setAppStatus(data?.ciConfig?.status || EDITOR_APP_STATUS_ERROR);
},
error() {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
},
watchLoading(isLoading) {
if (isLoading) {
this.setAppStatus(EDITOR_APP_STATUS_LOADING);
}
},
},
appStatus: {
query: getAppStatus,
},
currentBranch: {
query: getCurrentBranch,
},
isNewCiConfigFile: {
query: getIsNewCiConfigFile,
},
},
computed: {
hasUnsavedChanges() {
return this.lastCommittedContent !== this.currentCiFileContent;
},
isBlobContentLoading() {
return this.$apollo.queries.initialCiFileContent.loading;
},
isCiConfigDataLoading() {
return this.$apollo.queries.ciConfigData.loading;
},
isEmpty() {
return this.currentCiFileContent === '';
},
},
i18n: {
tabEdit: s__('Pipelines|Edit'),
tabGraph: s__('Pipelines|Visualize'),
tabLint: s__('Pipelines|Lint'),
},
watch: {
isEmpty(flag) {
if (flag) {
this.setAppStatus(EDITOR_APP_STATUS_EMPTY);
}
},
},
methods: {
handleBlobContentError(error = {}) {
const { networkError } = error;
const { response } = networkError;
// 404 for missing CI file
// 400 for blank projects with no repository
if (
response?.status === httpStatusCodes.NOT_FOUND ||
response?.status === httpStatusCodes.BAD_REQUEST
) {
this.setAppStatus(EDITOR_APP_STATUS_EMPTY);
this.showStartScreen = true;
} else {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
}
},
hideFailure() {
this.showFailure = false;
},
hideSuccess() {
this.showSuccess = false;
},
async refetchContent() {
this.$apollo.queries.initialCiFileContent.skip = false;
await this.$apollo.queries.initialCiFileContent.refetch();
},
reportFailure(type, reasons = []) {
this.setAppStatus(EDITOR_APP_STATUS_ERROR);
window.scrollTo({ top: 0, behavior: 'smooth' });
this.showFailure = true;
this.failureType = type;
this.failureReasons = reasons;
},
reportSuccess(type) {
window.scrollTo({ top: 0, behavior: 'smooth' });
this.showSuccess = true;
this.successType = type;
},
resetContent() {
this.currentCiFileContent = this.lastCommittedContent;
},
setAppStatus(appStatus) {
this.$apollo.getClient().writeQuery({ query: getAppStatus, data: { appStatus } });
},
setNewEmptyCiConfigFile() {
this.$apollo
.getClient()
.writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: true } });
this.showStartScreen = false;
},
showErrorAlert({ type, reasons = [] }) {
this.reportFailure(type, reasons);
},
updateCiConfig(ciFileContent) {
this.currentCiFileContent = ciFileContent;
},
updateOnCommit({ type }) {
this.reportSuccess(type);
if (this.isNewCiConfigFile) {
this.$apollo
.getClient()
.writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: false } });
}
// Keep track of the latest commited content to know
// if the user has made changes to the file that are unsaved.
this.lastCommittedContent = this.currentCiFileContent;
},
},
};
</script>
<template>
<div class="gl-mt-4 gl-relative">
<gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
<pipeline-editor-empty-state
v-else-if="showStartScreen"
@createEmptyConfigFile="setNewEmptyCiConfigFile"
@refetchContent="refetchContent"
/>
<div v-else>
<pipeline-editor-messages
:failure-type="failureType"
:failure-reasons="failureReasons"
:show-failure="showFailure"
:show-success="showSuccess"
:success-type="successType"
@hide-success="hideSuccess"
@hide-failure="hideFailure"
/>
<pipeline-editor-home
:ci-config-data="ciConfigData"
:ci-file-content="currentCiFileContent"
:is-new-ci-config-file="isNewCiConfigFile"
@commit="updateOnCommit"
@resetContent="resetContent"
@showError="showErrorAlert"
@refetchContent="refetchContent"
@updateCiConfig="updateCiConfig"
/>
<confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" />
</div>
</div>
</template>