Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 21:18:33 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 21:18:33 +0300
commitf64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch)
treea2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /app/assets/javascripts/pipeline_editor
parentbfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff)
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'app/assets/javascripts/pipeline_editor')
-rw-r--r--app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue16
-rw-r--r--app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue34
-rw-r--r--app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue2
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue47
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue120
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue18
-rw-r--r--app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue56
-rw-r--r--app/assets/javascripts/pipeline_editor/constants.js4
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql3
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql3
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/client/is_new_ci_config_file.graphql3
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql17
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/resolvers.js23
-rw-r--r--app/assets/javascripts/pipeline_editor/index.js4
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue91
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue1
16 files changed, 379 insertions, 63 deletions
diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue
index 9279273283e..b088678fee8 100644
--- a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue
+++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue
@@ -21,7 +21,7 @@ export default {
GlSprintf,
},
props: {
- defaultBranch: {
+ currentBranch: {
type: String,
required: false,
default: '',
@@ -40,23 +40,23 @@ export default {
data() {
return {
message: this.defaultMessage,
- branch: this.defaultBranch,
openMergeRequest: false,
+ targetBranch: this.currentBranch,
};
},
computed: {
- isDefaultBranch() {
- return this.branch === this.defaultBranch;
+ isCurrentBranchTarget() {
+ return this.targetBranch === this.currentBranch;
},
submitDisabled() {
- return !(this.message && this.branch);
+ return !(this.message && this.targetBranch);
},
},
methods: {
onSubmit() {
this.$emit('submit', {
message: this.message,
- branch: this.branch,
+ targetBranch: this.targetBranch,
openMergeRequest: this.openMergeRequest,
});
},
@@ -100,12 +100,12 @@ export default {
>
<gl-form-input
id="target-branch-field"
- v-model="branch"
+ v-model="targetBranch"
class="gl-font-monospace!"
required
/>
<gl-form-checkbox
- v-if="!isDefaultBranch"
+ v-if="!isCurrentBranchTarget"
v-model="openMergeRequest"
data-testid="new-mr-checkbox"
class="gl-mt-3"
diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue
index b40c9a48903..14a4a9d5710 100644
--- a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue
+++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue
@@ -1,9 +1,16 @@
<script>
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale';
-import { COMMIT_FAILURE, COMMIT_SUCCESS } from '../../constants';
+import {
+ COMMIT_ACTION_CREATE,
+ COMMIT_ACTION_UPDATE,
+ COMMIT_FAILURE,
+ COMMIT_SUCCESS,
+} from '../../constants';
import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql';
import getCommitSha from '../../graphql/queries/client/commit_sha.graphql';
+import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql';
+import getIsNewCiConfigFile from '../../graphql/queries/client/is_new_ci_config_file.graphql';
import CommitForm from './commit_form.vue';
@@ -21,7 +28,7 @@ export default {
components: {
CommitForm,
},
- inject: ['projectFullPath', 'ciConfigPath', 'defaultBranch', 'newMergeRequestPath'],
+ inject: ['projectFullPath', 'ciConfigPath', 'newMergeRequestPath'],
props: {
ciFileContent: {
type: String,
@@ -31,15 +38,25 @@ export default {
data() {
return {
commit: {},
+ isNewCiConfigFile: false,
isSaving: false,
};
},
apollo: {
+ isNewCiConfigFile: {
+ query: getIsNewCiConfigFile,
+ },
commitSha: {
query: getCommitSha,
},
+ currentBranch: {
+ query: getCurrentBranch,
+ },
},
computed: {
+ action() {
+ return this.isNewCiConfigFile ? COMMIT_ACTION_CREATE : COMMIT_ACTION_UPDATE;
+ },
defaultCommitMessage() {
return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath });
},
@@ -49,13 +66,13 @@ export default {
const url = mergeUrlParams(
{
[MR_SOURCE_BRANCH]: sourceBranch,
- [MR_TARGET_BRANCH]: this.defaultBranch,
+ [MR_TARGET_BRANCH]: this.currentBranch,
},
this.newMergeRequestPath,
);
redirectTo(url);
},
- async onCommitSubmit({ message, branch, openMergeRequest }) {
+ async onCommitSubmit({ message, targetBranch, openMergeRequest }) {
this.isSaving = true;
try {
@@ -66,9 +83,10 @@ export default {
} = await this.$apollo.mutate({
mutation: commitCIFile,
variables: {
+ action: this.action,
projectPath: this.projectFullPath,
- branch,
- startBranch: this.defaultBranch,
+ branch: targetBranch,
+ startBranch: this.currentBranch,
message,
filePath: this.ciConfigPath,
content: this.ciFileContent,
@@ -86,7 +104,7 @@ export default {
if (errors?.length) {
this.$emit('showError', { type: COMMIT_FAILURE, reasons: errors });
} else if (openMergeRequest) {
- this.redirectToNewMergeRequest(branch);
+ this.redirectToNewMergeRequest(targetBranch);
} else {
this.$emit('commit', { type: COMMIT_SUCCESS });
}
@@ -105,7 +123,7 @@ export default {
<template>
<commit-form
- :default-branch="defaultBranch"
+ :current-branch="currentBranch"
:default-message="defaultCommitMessage"
:is-saving="isSaving"
@cancel="onCommitCancel"
diff --git a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue
index 007faa4ed0d..f36b22f33c3 100644
--- a/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue
+++ b/app/assets/javascripts/pipeline_editor/components/editor/ci_config_merged_preview.vue
@@ -82,7 +82,7 @@ export default {
</gl-alert>
<div v-else>
<div class="gl-display-flex gl-align-items-center">
- <gl-icon :size="18" name="lock" class="gl-text-gray-500 gl-mr-3" />
+ <gl-icon :size="18" name="lock" use-deprecated-sizes class="gl-text-gray-500 gl-mr-3" />
{{ $options.i18n.viewOnlyMessage }}
</div>
<div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1">
diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue
index ab41c0170e9..7a35e31e9ce 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_header.vue
@@ -1,13 +1,40 @@
<script>
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import PipelineStatus from './pipeline_status.vue';
import ValidationSegment from './validation_segment.vue';
+const baseClasses = ['gl-p-5', 'gl-bg-gray-10', 'gl-border-solid', 'gl-border-gray-100'];
+
+const pipelineStatusClasses = [
+ ...baseClasses,
+ 'gl-border-1',
+ 'gl-border-b-0!',
+ 'gl-rounded-top-base',
+];
+
+const validationSegmentClasses = [...baseClasses, 'gl-border-1', 'gl-rounded-base'];
+
+const validationSegmentWithPipelineStatusClasses = [
+ ...baseClasses,
+ 'gl-border-1',
+ 'gl-rounded-bottom-left-base',
+ 'gl-rounded-bottom-right-base',
+];
+
export default {
- validationSegmentClasses:
- 'gl-p-5 gl-bg-gray-10 gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base',
+ pipelineStatusClasses,
+ validationSegmentClasses,
+ validationSegmentWithPipelineStatusClasses,
components: {
+ PipelineStatus,
ValidationSegment,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
+ ciFileContent: {
+ type: String,
+ required: true,
+ },
ciConfigData: {
type: Object,
required: true,
@@ -17,13 +44,27 @@ export default {
required: true,
},
},
+ computed: {
+ showPipelineStatus() {
+ return this.glFeatures.pipelineStatusForPipelineEditor;
+ },
+ // make sure corners are rounded correctly depending on if
+ // pipeline status is rendered
+ validationStyling() {
+ return this.showPipelineStatus
+ ? this.$options.validationSegmentWithPipelineStatusClasses
+ : this.$options.validationSegmentClasses;
+ },
+ },
};
</script>
<template>
<div class="gl-mb-5">
+ <pipeline-status v-if="showPipelineStatus" :class="$options.pipelineStatusClasses" />
<validation-segment
- :class="$options.validationSegmentClasses"
+ :class="validationStyling"
:loading="isCiConfigDataLoading"
+ :ci-file-content="ciFileContent"
:ci-config="ciConfigData"
/>
</div>
diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
new file mode 100644
index 00000000000..b1ea464be99
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
@@ -0,0 +1,120 @@
+<script>
+import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { s__ } from '~/locale';
+import getCommitSha from '~/pipeline_editor/graphql/queries/client/commit_sha.graphql';
+import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+
+const POLL_INTERVAL = 10000;
+export const i18n = {
+ fetchError: s__('Pipeline|We are currently unable to fetch pipeline data'),
+ fetchLoading: s__('Pipeline|Checking pipeline status'),
+ pipelineInfo: s__(
+ `Pipeline|Pipeline %{idStart}#%{idEnd} %{statusStart}%{statusEnd} for %{commitStart}%{commitEnd}`,
+ ),
+};
+
+export default {
+ i18n,
+ components: {
+ CiIcon,
+ GlIcon,
+ GlLink,
+ GlLoadingIcon,
+ GlSprintf,
+ },
+ inject: ['projectFullPath'],
+ apollo: {
+ commitSha: {
+ query: getCommitSha,
+ },
+ pipeline: {
+ query: getPipelineQuery,
+ variables() {
+ return {
+ fullPath: this.projectFullPath,
+ sha: this.commitSha,
+ };
+ },
+ update: (data) => {
+ const { id, commitPath = '', shortSha = '', detailedStatus = {} } =
+ data.project?.pipeline || {};
+
+ return {
+ id,
+ commitPath,
+ shortSha,
+ detailedStatus,
+ };
+ },
+ error() {
+ this.hasError = true;
+ },
+ pollInterval: POLL_INTERVAL,
+ },
+ },
+ data() {
+ return {
+ hasError: false,
+ };
+ },
+ computed: {
+ hasPipelineData() {
+ return Boolean(this.$apollo.queries.pipeline?.id);
+ },
+ isQueryLoading() {
+ return this.$apollo.queries.pipeline.loading && !this.hasPipelineData;
+ },
+ status() {
+ return this.pipeline.detailedStatus;
+ },
+ pipelineId() {
+ return getIdFromGraphQLId(this.pipeline.id);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-white-space-nowrap gl-max-w-full">
+ <template v-if="isQueryLoading">
+ <gl-loading-icon class="gl-mr-auto gl-display-inline-block" size="sm" />
+ <span data-testid="pipeline-loading-msg">{{ $options.i18n.fetchLoading }}</span>
+ </template>
+ <template v-else-if="hasError">
+ <gl-icon class="gl-mr-auto" name="warning-solid" />
+ <span data-testid="pipeline-error-msg">{{ $options.i18n.fetchError }}</span>
+ </template>
+ <template v-else>
+ <a :href="status.detailsPath" class="gl-mr-auto">
+ <ci-icon :status="status" :size="18" />
+ </a>
+ <span class="gl-font-weight-bold">
+ <gl-sprintf :message="$options.i18n.pipelineInfo">
+ <template #id="{ content }">
+ <gl-link
+ :href="status.detailsPath"
+ class="pipeline-id gl-font-weight-normal pipeline-number"
+ target="_blank"
+ data-testid="pipeline-id"
+ >
+ {{ content }}{{ pipelineId }}</gl-link
+ >
+ </template>
+ <template #status>{{ status.text }}</template>
+ <template #commit>
+ <gl-link
+ :href="pipeline.commitPath"
+ class="commit-sha gl-font-weight-normal"
+ target="_blank"
+ data-testid="pipeline-commit"
+ >
+ {{ pipeline.shortSha }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
index 94fb3a66fdd..541ab74b177 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
@@ -5,6 +5,9 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import { CI_CONFIG_STATUS_VALID } from '../../constants';
export const i18n = {
+ empty: __(
+ "We'll continuously validate your pipeline configuration. The validation results will appear here.",
+ ),
learnMore: __('Learn more'),
loading: s__('Pipelines|Validating GitLab CI configuration…'),
invalid: s__('Pipelines|This GitLab CI configuration is invalid.'),
@@ -26,6 +29,10 @@ export default {
},
},
props: {
+ ciFileContent: {
+ type: String,
+ required: true,
+ },
ciConfig: {
type: Object,
required: false,
@@ -38,17 +45,22 @@ export default {
},
},
computed: {
+ isEmpty() {
+ return !this.ciFileContent;
+ },
isValid() {
return this.ciConfig?.status === CI_CONFIG_STATUS_VALID;
},
icon() {
- if (this.isValid) {
+ if (this.isValid || this.isEmpty) {
return 'check';
}
return 'warning-solid';
},
message() {
- if (this.isValid) {
+ if (this.isEmpty) {
+ return this.$options.i18n.empty;
+ } else if (this.isValid) {
return this.$options.i18n.valid;
}
@@ -74,7 +86,7 @@ export default {
<tooltip-on-truncate :title="message" class="gl-text-truncate">
<gl-icon :name="icon" /> <span data-testid="validationMsg">{{ message }}</span>
</tooltip-on-truncate>
- <span class="gl-flex-shrink-0 gl-pl-2">
+ <span v-if="!isEmpty" class="gl-flex-shrink-0 gl-pl-2">
<gl-link data-testid="learnMoreLink" :href="ymlHelpPagePath">
{{ $options.i18n.learnMore }}
</gl-link>
diff --git a/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
new file mode 100644
index 00000000000..d4f04a0d055
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/components/ui/pipeline_editor_empty_state.vue
@@ -0,0 +1,56 @@
+<script>
+import { GlButton, GlSprintf } from '@gitlab/ui';
+import { __ } from '~/locale';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+
+export default {
+ components: {
+ GlButton,
+ GlSprintf,
+ },
+ i18n: {
+ title: __('Optimize your workflow with CI/CD Pipelines'),
+ body: __(
+ 'Create a new %{codeStart}.gitlab-ci.yml%{codeEnd} file at the root of the repository to get started.',
+ ),
+ btnText: __('Create new CI/CD pipeline'),
+ },
+ mixins: [glFeatureFlagsMixin()],
+ inject: {
+ emptyStateIllustrationPath: {
+ default: '',
+ },
+ },
+ computed: {
+ showCTAButton() {
+ return this.glFeatures.pipelineEditorEmptyStateAction;
+ },
+ },
+ methods: {
+ createEmptyConfigFile() {
+ this.$emit('createEmptyConfigFile');
+ },
+ },
+};
+</script>
+<template>
+ <div class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-mt-11">
+ <img :src="emptyStateIllustrationPath" />
+ <h1 class="gl-font-size-h1">{{ $options.i18n.title }}</h1>
+ <p class="gl-mt-3">
+ <gl-sprintf :message="$options.i18n.body">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <gl-button
+ v-if="showCTAButton"
+ variant="confirm"
+ class="gl-mt-3"
+ @click="createEmptyConfigFile"
+ >
+ {{ $options.i18n.btnText }}
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js
index e676fdeae02..353deafe770 100644
--- a/app/assets/javascripts/pipeline_editor/constants.js
+++ b/app/assets/javascripts/pipeline_editor/constants.js
@@ -5,7 +5,6 @@ export const COMMIT_FAILURE = 'COMMIT_FAILURE';
export const COMMIT_SUCCESS = 'COMMIT_SUCCESS';
export const DEFAULT_FAILURE = 'DEFAULT_FAILURE';
-export const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE';
export const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
export const CREATE_TAB = 'CREATE_TAB';
@@ -14,3 +13,6 @@ export const MERGED_TAB = 'MERGED_TAB';
export const VISUALIZE_TAB = 'VISUALIZE_TAB';
export const TABS_WITH_COMMIT_FORM = [CREATE_TAB, LINT_TAB, VISUALIZE_TAB];
+
+export const COMMIT_ACTION_CREATE = 'CREATE';
+export const COMMIT_ACTION_UPDATE = 'UPDATE';
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql
index 2af0cd5f6d4..3b2daa45a18 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql
@@ -1,4 +1,5 @@
mutation commitCIFile(
+ $action: CommitActionMode!
$projectPath: ID!
$branch: String!
$startBranch: String
@@ -14,7 +15,7 @@ mutation commitCIFile(
startBranch: $startBranch
message: $message
actions: [
- { action: UPDATE, filePath: $filePath, lastCommitId: $lastCommitId, content: $content }
+ { action: $action, filePath: $filePath, lastCommitId: $lastCommitId, content: $content }
]
}
) {
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql
new file mode 100644
index 00000000000..acd46013f5b
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql
@@ -0,0 +1,3 @@
+query getCurrentBranch {
+ currentBranch @client
+}
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/is_new_ci_config_file.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/is_new_ci_config_file.graphql
new file mode 100644
index 00000000000..8c2ca276f50
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/is_new_ci_config_file.graphql
@@ -0,0 +1,3 @@
+query getIsNewCiConfigFile {
+ isNewCiConfigFile @client
+}
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql
new file mode 100644
index 00000000000..7cc7f92fb60
--- /dev/null
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql
@@ -0,0 +1,17 @@
+query getPipeline($fullPath: ID!, $sha: String!) {
+ project(fullPath: $fullPath) @client {
+ pipeline(sha: $sha) {
+ commitPath
+ id
+ iid
+ shortSha
+ status
+ detailedStatus {
+ detailsPath
+ icon
+ group
+ text
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
index 81e75c32846..13f6200693b 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
+++ b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
@@ -11,6 +11,29 @@ export const resolvers = {
}),
};
},
+
+ /* eslint-disable @gitlab/require-i18n-strings */
+ project() {
+ return {
+ __typename: 'Project',
+ pipeline: {
+ __typename: 'Pipeline',
+ commitPath: `/-/commit/aabbccdd`,
+ id: 'gid://gitlab/Ci::Pipeline/118',
+ iid: '28',
+ shortSha: 'aabbccdd',
+ status: 'SUCCESS',
+ detailedStatus: {
+ __typename: 'DetailedStatus',
+ detailsPath: '/root/sample-ci-project/-/pipelines/118"',
+ group: 'success',
+ icon: 'status_success',
+ text: 'passed',
+ },
+ },
+ };
+ },
+ /* eslint-enable @gitlab/require-i18n-strings */
},
Mutation: {
lintCI: (_, { endpoint, content, dry_run }) => {
diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js
index dc427f55b5f..b17ec2d5c25 100644
--- a/app/assets/javascripts/pipeline_editor/index.js
+++ b/app/assets/javascripts/pipeline_editor/index.js
@@ -22,9 +22,11 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
const {
// Add to apollo cache as it can be updated by future queries
commitSha,
+ initialBranchName,
// Add to provide/inject API for static values
ciConfigPath,
defaultBranch,
+ emptyStateIllustrationPath,
lintHelpPagePath,
newMergeRequestPath,
projectFullPath,
@@ -41,6 +43,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
apolloProvider.clients.defaultClient.cache.writeData({
data: {
+ currentBranch: initialBranchName || defaultBranch,
commitSha,
},
});
@@ -51,6 +54,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
provide: {
ciConfigPath,
defaultBranch,
+ emptyStateIllustrationPath,
lintHelpPagePath,
newMergeRequestPath,
projectFullPath,
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index b4a818e2472..c1168979e9f 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -1,19 +1,16 @@
<script>
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import httpStatusCodes from '~/lib/utils/http_status';
-import { __, s__, sprintf } from '~/locale';
+import { __, s__ } from '~/locale';
import { unwrapStagesWithNeeds } from '~/pipelines/components/unwrapping_utils';
import ConfirmUnsavedChangesDialog from './components/ui/confirm_unsaved_changes_dialog.vue';
-import {
- COMMIT_FAILURE,
- COMMIT_SUCCESS,
- DEFAULT_FAILURE,
- LOAD_FAILURE_NO_FILE,
- LOAD_FAILURE_UNKNOWN,
-} from './constants';
+import PipelineEditorEmptyState from './components/ui/pipeline_editor_empty_state.vue';
+import { COMMIT_FAILURE, COMMIT_SUCCESS, DEFAULT_FAILURE, LOAD_FAILURE_UNKNOWN } from './constants';
import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.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 {
@@ -21,15 +18,13 @@ export default {
ConfirmUnsavedChangesDialog,
GlAlert,
GlLoadingIcon,
+ PipelineEditorEmptyState,
PipelineEditorHome,
},
inject: {
ciConfigPath: {
default: '',
},
- defaultBranch: {
- default: null,
- },
projectFullPath: {
default: '',
},
@@ -40,6 +35,8 @@ export default {
// Success and failure state
failureType: null,
failureReasons: [],
+ showStartScreen: false,
+ isNewCiConfigFile: false,
initialCiFileContent: '',
lastCommittedContent: '',
currentCiFileContent: '',
@@ -51,11 +48,18 @@ export default {
apollo: {
initialCiFileContent: {
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.defaultBranch,
+ ref: this.currentBranch,
};
},
update(data) {
@@ -94,6 +98,12 @@ export default {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
},
},
+ currentBranch: {
+ query: getCurrentBranch,
+ },
+ isNewCiConfigFile: {
+ query: getIsNewCiConfigFile,
+ },
},
computed: {
hasUnsavedChanges() {
@@ -102,21 +112,11 @@ export default {
isBlobContentLoading() {
return this.$apollo.queries.initialCiFileContent.loading;
},
- isBlobContentError() {
- return this.failureType === LOAD_FAILURE_NO_FILE;
- },
isCiConfigDataLoading() {
return this.$apollo.queries.ciConfigData.loading;
},
failure() {
switch (this.failureType) {
- case LOAD_FAILURE_NO_FILE:
- return {
- text: sprintf(this.$options.errorTexts[LOAD_FAILURE_NO_FILE], {
- filePath: this.ciConfigPath,
- }),
- variant: 'danger',
- };
case LOAD_FAILURE_UNKNOWN:
return {
text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN],
@@ -154,9 +154,6 @@ export default {
errorTexts: {
[COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'),
[DEFAULT_FAILURE]: __('Something went wrong on our end.'),
- [LOAD_FAILURE_NO_FILE]: s__(
- 'Pipelines|There is no %{filePath} file in this repository, please add one and visit the Pipeline Editor again.',
- ),
[LOAD_FAILURE_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'),
},
successTexts: {
@@ -173,7 +170,7 @@ export default {
response?.status === httpStatusCodes.NOT_FOUND ||
response?.status === httpStatusCodes.BAD_REQUEST
) {
- this.reportFailure(LOAD_FAILURE_NO_FILE);
+ this.showStartScreen = true;
} else {
this.reportFailure(LOAD_FAILURE_UNKNOWN);
}
@@ -186,17 +183,25 @@ export default {
this.showSuccessAlert = false;
},
reportFailure(type, reasons = []) {
+ window.scrollTo({ top: 0, behavior: 'smooth' });
this.showFailureAlert = true;
this.failureType = type;
this.failureReasons = reasons;
},
reportSuccess(type) {
+ window.scrollTo({ top: 0, behavior: 'smooth' });
this.showSuccessAlert = true;
this.successType = type;
},
resetContent() {
this.currentCiFileContent = this.lastCommittedContent;
},
+ setNewEmptyCiConfigFile() {
+ this.$apollo
+ .getClient()
+ .writeQuery({ query: getIsNewCiConfigFile, data: { isNewCiConfigFile: true } });
+ this.showStartScreen = false;
+ },
showErrorAlert({ type, reasons = [] }) {
this.reportFailure(type, reasons);
},
@@ -205,6 +210,12 @@ export default {
},
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;
@@ -214,18 +225,22 @@ export default {
</script>
<template>
- <div class="gl-mt-4">
- <gl-alert v-if="showSuccessAlert" :variant="success.variant" @dismiss="dismissSuccess">
- {{ success.text }}
- </gl-alert>
- <gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="dismissFailure">
- {{ failure.text }}
- <ul v-if="failureReasons.length" class="gl-mb-0">
- <li v-for="reason in failureReasons" :key="reason">{{ reason }}</li>
- </ul>
- </gl-alert>
+ <div class="gl-mt-4 gl-relative">
<gl-loading-icon v-if="isBlobContentLoading" size="lg" class="gl-m-3" />
- <div v-else-if="!isBlobContentError" class="gl-mt-4">
+ <pipeline-editor-empty-state
+ v-else-if="showStartScreen"
+ @createEmptyConfigFile="setNewEmptyCiConfigFile"
+ />
+ <div v-else>
+ <gl-alert v-if="showSuccessAlert" :variant="success.variant" @dismiss="dismissSuccess">
+ {{ success.text }}
+ </gl-alert>
+ <gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="dismissFailure">
+ {{ failure.text }}
+ <ul v-if="failureReasons.length" class="gl-mb-0">
+ <li v-for="reason in failureReasons" :key="reason">{{ reason }}</li>
+ </ul>
+ </gl-alert>
<pipeline-editor-home
:is-ci-config-data-loading="isCiConfigDataLoading"
:ci-config-data="ciConfigData"
@@ -235,7 +250,7 @@ export default {
@showError="showErrorAlert"
@updateCiConfig="updateCiConfig"
/>
+ <confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" />
</div>
- <confirm-unsaved-changes-dialog :has-unsaved-changes="hasUnsavedChanges" />
</div>
</template>
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
index 8c9aad6ed87..ef46040153f 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
@@ -45,6 +45,7 @@ export default {
<template>
<div>
<pipeline-editor-header
+ :ci-file-content="ciFileContent"
:ci-config-data="ciConfigData"
:is-ci-config-data-loading="isCiConfigDataLoading"
/>