Commit 9b22eb48 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch 'show-merged-version-of-ci-config' into 'master'

Show merged version of ci config [RUN AS-IF-FOSS] [RUN ALL RSPEC]

See merge request gitlab-org/gitlab!53299
parents 8eb330d8 2c2e5351
<script>
import { uniqueId } from 'lodash';
import { GlAlert, GlIcon } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { DEFAULT, INVALID_CI_CONFIG } from '~/pipelines/constants';
import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
import EditorLite from '~/vue_shared/components/editor_lite.vue';
export default {
i18n: {
viewOnlyMessage: s__('Pipelines|Merged YAML is view only'),
},
errorTexts: {
[INVALID_CI_CONFIG]: __('Your CI configuration file is invalid.'),
[DEFAULT]: __('An unknown error occurred.'),
},
components: {
EditorLite,
GlAlert,
GlIcon,
},
inject: ['ciConfigPath'],
props: {
ciConfigData: {
type: Object,
required: true,
},
},
data() {
return {
failureType: null,
};
},
computed: {
failure() {
switch (this.failureType) {
case INVALID_CI_CONFIG:
return this.$options.errorTexts[INVALID_CI_CONFIG];
default:
return this.$options.errorTexts[DEFAULT];
}
},
fileGlobalId() {
return `${this.ciConfigPath}-${uniqueId()}`;
},
hasError() {
return this.failureType;
},
isInvalidConfiguration() {
return this.ciConfigData.status === CI_CONFIG_STATUS_INVALID;
},
mergedYaml() {
return this.ciConfigData.mergedYaml;
},
},
watch: {
ciConfigData: {
immediate: true,
handler() {
if (this.isInvalidConfiguration) {
this.reportFailure(INVALID_CI_CONFIG);
} else if (this.hasError) {
this.resetFailure();
}
},
},
},
methods: {
reportFailure(errorType) {
this.failureType = errorType;
},
resetFailure() {
this.failureType = null;
},
},
};
</script>
<template>
<div>
<gl-alert v-if="hasError" variant="danger" :dismissible="false">
{{ failure }}
</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" />
{{ $options.i18n.viewOnlyMessage }}
</div>
<div class="gl-mt-3 gl-border-solid gl-border-gray-100 gl-border-1">
<editor-lite
ref="editor"
:value="mergedYaml"
:file-name="ciConfigPath"
:file-global-id="fileGlobalId"
:editor-options="{ readOnly: true }"
v-on="$listeners"
/>
</div>
</div>
</div>
</template>
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import EditorLite from '~/vue_shared/components/editor_lite.vue'; import EditorLite from '~/vue_shared/components/editor_lite.vue';
import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext'; import { CiSchemaExtension } from '~/editor/extensions/editor_ci_schema_ext';
import { EDITOR_READY_EVENT } from '~/editor/constants'; import { EDITOR_READY_EVENT } from '~/editor/constants';
import getCommitSha from '../graphql/queries/client/commit_sha.graphql'; import getCommitSha from '../../graphql/queries/client/commit_sha.graphql';
export default { export default {
components: { components: {
......
<script> <script>
import { GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui'; import { GlAlert, GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
CI_CONFIG_STATUS_INVALID,
CREATE_TAB,
LINT_TAB,
MERGED_TAB,
VISUALIZE_TAB,
} from '../constants';
import CiConfigMergedPreview from './editor/ci_config_merged_preview.vue';
import CiLint from './lint/ci_lint.vue'; import CiLint from './lint/ci_lint.vue';
import EditorTab from './ui/editor_tab.vue'; import EditorTab from './ui/editor_tab.vue';
import TextEditor from './text_editor.vue'; import TextEditor from './editor/text_editor.vue';
export default { export default {
i18n: { i18n: {
tabEdit: s__('Pipelines|Write pipeline configuration'), tabEdit: s__('Pipelines|Write pipeline configuration'),
tabGraph: s__('Pipelines|Visualize'), tabGraph: s__('Pipelines|Visualize'),
tabLint: s__('Pipelines|Lint'), tabLint: s__('Pipelines|Lint'),
tabMergedYaml: s__('Pipelines|View merged YAML'),
},
errorTexts: {
loadMergedYaml: s__('Pipelines|Could not load merged YAML content'),
},
tabConstants: {
CREATE_TAB,
LINT_TAB,
MERGED_TAB,
VISUALIZE_TAB,
}, },
components: { components: {
CiConfigMergedPreview,
CiLint, CiLint,
EditorTab, EditorTab,
GlAlert,
GlLoadingIcon, GlLoadingIcon,
GlTab, GlTab,
GlTabs, GlTabs,
...@@ -38,25 +58,64 @@ export default { ...@@ -38,25 +58,64 @@ export default {
default: false, default: false,
}, },
}, },
computed: {
hasMergedYamlLoadError() {
return (
!this.ciConfigData?.mergedYaml && this.ciConfigData.status !== CI_CONFIG_STATUS_INVALID
);
},
},
methods: {
setCurrentTab(tabName) {
this.$emit('set-current-tab', tabName);
},
},
}; };
</script> </script>
<template> <template>
<gl-tabs class="file-editor gl-mb-3"> <gl-tabs class="file-editor gl-mb-3">
<editor-tab :title="$options.i18n.tabEdit" lazy data-testid="editor-tab"> <editor-tab
class="gl-mb-3"
:title="$options.i18n.tabEdit"
lazy
data-testid="editor-tab"
@click="setCurrentTab($options.tabConstants.CREATE_TAB)"
>
<text-editor :value="ciFileContent" v-on="$listeners" /> <text-editor :value="ciFileContent" v-on="$listeners" />
</editor-tab> </editor-tab>
<gl-tab <gl-tab
v-if="glFeatures.ciConfigVisualizationTab" v-if="glFeatures.ciConfigVisualizationTab"
class="gl-mb-3"
:title="$options.i18n.tabGraph" :title="$options.i18n.tabGraph"
lazy lazy
data-testid="visualization-tab" data-testid="visualization-tab"
@click="setCurrentTab($options.tabConstants.VISUALIZE_TAB)"
> >
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" /> <gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<pipeline-graph v-else :pipeline-data="ciConfigData" /> <pipeline-graph v-else :pipeline-data="ciConfigData" />
</gl-tab> </gl-tab>
<editor-tab :title="$options.i18n.tabLint" data-testid="lint-tab"> <editor-tab
class="gl-mb-3"
:title="$options.i18n.tabLint"
data-testid="lint-tab"
@click="setCurrentTab($options.tabConstants.LINT_TAB)"
>
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" /> <gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<ci-lint v-else :ci-config="ciConfigData" /> <ci-lint v-else :ci-config="ciConfigData" />
</editor-tab> </editor-tab>
<gl-tab
v-if="glFeatures.ciConfigMergedTab"
class="gl-mb-3"
:title="$options.i18n.tabMergedYaml"
lazy
data-testid="merged-tab"
@click="setCurrentTab($options.tabConstants.MERGED_TAB)"
>
<gl-loading-icon v-if="isCiConfigDataLoading" size="lg" class="gl-m-3" />
<gl-alert v-else-if="hasMergedYamlLoadError" variant="danger" :dismissible="false">
{{ $options.errorTexts.loadMergedYaml }}
</gl-alert>
<ci-config-merged-preview v-else :ci-config-data="ciConfigData" v-on="$listeners" />
</gl-tab>
</gl-tabs> </gl-tabs>
</template> </template>
...@@ -7,3 +7,10 @@ export const COMMIT_SUCCESS = 'COMMIT_SUCCESS'; ...@@ -7,3 +7,10 @@ export const COMMIT_SUCCESS = 'COMMIT_SUCCESS';
export const DEFAULT_FAILURE = 'DEFAULT_FAILURE'; export const DEFAULT_FAILURE = 'DEFAULT_FAILURE';
export const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE'; export const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE';
export const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN'; export const LOAD_FAILURE_UNKNOWN = 'LOAD_FAILURE_UNKNOWN';
export const CREATE_TAB = 'CREATE_TAB';
export const LINT_TAB = 'LINT_TAB';
export const MERGED_TAB = 'MERGED_TAB';
export const VISUALIZE_TAB = 'VISUALIZE_TAB';
export const TABS_WITH_COMMIT_FORM = [CREATE_TAB, LINT_TAB, VISUALIZE_TAB];
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
query getCiConfigData($projectPath: ID!, $content: String!) { query getCiConfigData($projectPath: ID!, $content: String!) {
ciConfig(projectPath: $projectPath, content: $content) { ciConfig(projectPath: $projectPath, content: $content) {
errors errors
mergedYaml
status status
stages { stages {
...PipelineStagesConnection ...PipelineStagesConnection
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import CommitSection from './components/commit/commit_section.vue'; import CommitSection from './components/commit/commit_section.vue';
import PipelineEditorTabs from './components/pipeline_editor_tabs.vue'; import PipelineEditorTabs from './components/pipeline_editor_tabs.vue';
import PipelineEditorHeader from './components/header/pipeline_editor_header.vue'; import PipelineEditorHeader from './components/header/pipeline_editor_header.vue';
import { TABS_WITH_COMMIT_FORM, CREATE_TAB } from './constants';
export default { export default {
components: { components: {
...@@ -23,6 +24,21 @@ export default { ...@@ -23,6 +24,21 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
currentTab: CREATE_TAB,
};
},
computed: {
showCommitForm() {
return TABS_WITH_COMMIT_FORM.includes(this.currentTab);
},
},
methods: {
setCurrentTab(tabName) {
this.currentTab = tabName;
},
},
}; };
</script> </script>
...@@ -37,7 +53,8 @@ export default { ...@@ -37,7 +53,8 @@ export default {
:ci-file-content="ciFileContent" :ci-file-content="ciFileContent"
:is-ci-config-data-loading="isCiConfigDataLoading" :is-ci-config-data-loading="isCiConfigDataLoading"
v-on="$listeners" v-on="$listeners"
@set-current-tab="setCurrentTab"
/> />
<commit-section :ci-file-content="ciFileContent" v-on="$listeners" /> <commit-section v-if="showCommitForm" :ci-file-content="ciFileContent" v-on="$listeners" />
</div> </div>
</template> </template>
...@@ -4,6 +4,7 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController ...@@ -4,6 +4,7 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
before_action :check_can_collaborate! before_action :check_can_collaborate!
before_action do before_action do
push_frontend_feature_flag(:ci_config_visualization_tab, @project, default_enabled: :yaml) push_frontend_feature_flag(:ci_config_visualization_tab, @project, default_enabled: :yaml)
push_frontend_feature_flag(:ci_config_merged_tab, @project, default_enabled: :yaml)
end end
feature_category :pipeline_authoring feature_category :pipeline_authoring
......
---
name: ci_config_merged_tab
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53299
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/301103
milestone: '13.9'
type: development
group: group::pipeline authoring
default_enabled: false
...@@ -25,6 +25,7 @@ From the pipeline editor page you can: ...@@ -25,6 +25,7 @@ From the pipeline editor page you can:
- Do a deeper [lint](#lint-ci-configuration) of your configuration, that verifies it with any configuration - Do a deeper [lint](#lint-ci-configuration) of your configuration, that verifies it with any configuration
added with the [`include`](../yaml/README.md#include) keyword. added with the [`include`](../yaml/README.md#include) keyword.
- See a [visualization](#visualize-ci-configuration) of the current configuration. - See a [visualization](#visualize-ci-configuration) of the current configuration.
- View an [expanded](#view-expanded-configuration) version of your configuration.
- [Commit](#commit-changes-to-ci-configuration) the changes to a specific branch. - [Commit](#commit-changes-to-ci-configuration) the changes to a specific branch.
NOTE: NOTE:
...@@ -101,6 +102,40 @@ To enable it: ...@@ -101,6 +102,40 @@ To enable it:
Feature.enable(:ci_config_visualization_tab) Feature.enable(:ci_config_visualization_tab)
``` ```
## View expanded configuration
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/246801) in GitLab 13.9.
> - It is [deployed behind a feature flag](../../user/feature_flags.md), disabled by default.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-expanded-configuration). **(FREE SELF)**
To view the fully expanded CI/CD configuration as one combined file, go to the
pipeline editor's **View merged YAML** tab. This tab displays an expanded configuration
where:
- Configuration imported with [`include`](../yaml/README.md#include) is copied into the view.
- Jobs that use [`extends`](../yaml/README.md#extends) display with the
[extended configuration merged into the job](../yaml/README.md#merge-details).
- YAML anchors are [replaced with the linked configuration](../yaml/README.md#anchors).
### Enable or disable expanded configuration **(FREE SELF)**
Expanded CI/CD configuration is under development and not ready for production use.
It is deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can opt to enable it.
To enable it:
```ruby
Feature.enable(:ci_config_visualization_tab)
```
To disable it:
```ruby
Feature.disable(:ci_config_visualization_tab)
```
## Commit changes to CI configuration ## Commit changes to CI configuration
The commit form appears at the bottom of each tab in the editor so you can commit The commit form appears at the bottom of each tab in the editor so you can commit
......
...@@ -21672,6 +21672,9 @@ msgstr "" ...@@ -21672,6 +21672,9 @@ msgstr ""
msgid "Pipelines|Copy trigger token" msgid "Pipelines|Copy trigger token"
msgstr "" msgstr ""
msgid "Pipelines|Could not load merged YAML content"
msgstr ""
msgid "Pipelines|Description" msgid "Pipelines|Description"
msgstr "" msgstr ""
...@@ -21708,6 +21711,9 @@ msgstr "" ...@@ -21708,6 +21711,9 @@ msgstr ""
msgid "Pipelines|Loading Pipelines" msgid "Pipelines|Loading Pipelines"
msgstr "" msgstr ""
msgid "Pipelines|Merged YAML is view only"
msgstr ""
msgid "Pipelines|More Information" msgid "Pipelines|More Information"
msgstr "" msgstr ""
...@@ -21780,6 +21786,9 @@ msgstr "" ...@@ -21780,6 +21786,9 @@ msgstr ""
msgid "Pipelines|Validating GitLab CI configuration…" msgid "Pipelines|Validating GitLab CI configuration…"
msgstr "" msgstr ""
msgid "Pipelines|View merged YAML"
msgstr ""
msgid "Pipelines|Visualize" msgid "Pipelines|Visualize"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlIcon } from '@gitlab/ui';
import { EDITOR_READY_EVENT } from '~/editor/constants';
import { INVALID_CI_CONFIG } from '~/pipelines/constants';
import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue';
import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants';
import { mockLintResponse, mockCiConfigPath } from '../../mock_data';
describe('Text editor component', () => {
let wrapper;
const MockEditorLite = {
template: '<div/>',
props: ['value', 'fileName', 'editorOptions'],
mounted() {
this.$emit(EDITOR_READY_EVENT);
},
};
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(CiConfigMergedPreview, {
propsData: {
ciConfigData: mockLintResponse,
...props,
},
provide: {
ciConfigPath: mockCiConfigPath,
},
stubs: {
EditorLite: MockEditorLite,
},
});
};
const findAlert = () => wrapper.findComponent(GlAlert);
const findIcon = () => wrapper.findComponent(GlIcon);
const findEditor = () => wrapper.findComponent(MockEditorLite);
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('when status is invalid', () => {
beforeEach(() => {
createComponent({ props: { ciConfigData: { status: CI_CONFIG_STATUS_INVALID } } });
});
it('show an error message', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[INVALID_CI_CONFIG]);
});
it('hides the editor', () => {
expect(findEditor().exists()).toBe(false);
});
});
describe('when status is valid', () => {
beforeEach(() => {
createComponent();
});
it('shows an information message that the section is not editable', () => {
expect(findIcon().exists()).toBe(true);
expect(wrapper.text()).toContain(wrapper.vm.$options.i18n.viewOnlyMessage);
});
it('contains an editor', () => {
expect(findEditor().exists()).toBe(true);
});
it('editor contains the value provided', () => {
expect(findEditor().props('value')).toBe(mockLintResponse.mergedYaml);
});
it('editor is configured for the CI config path', () => {
expect(findEditor().props('fileName')).toBe(mockCiConfigPath);
});
it('editor is readonly', () => {
expect(findEditor().props('editorOptions')).toMatchObject({
readOnly: true,
});
});
});
});
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { EDITOR_READY_EVENT } from '~/editor/constants'; import { EDITOR_READY_EVENT } from '~/editor/constants';
import TextEditor from '~/pipeline_editor/components/text_editor.vue'; import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue';
import { import {
mockCiConfigPath, mockCiConfigPath,
mockCiYml, mockCiYml,
mockCommitSha, mockCommitSha,
mockProjectPath, mockProjectPath,
mockProjectNamespace, mockProjectNamespace,
} from '../mock_data'; } from '../../mock_data';
describe('Pipeline Editor | Text editor component', () => { describe('Pipeline Editor | Text editor component', () => {
let wrapper; let wrapper;
......
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { shallowMount, mount } from '@vue/test-utils'; import { shallowMount, mount } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue'; import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
import CiLint from '~/pipeline_editor/components/lint/ci_lint.vue'; import CiConfigMergedPreview from '~/pipeline_editor/components/editor/ci_config_merged_preview.vue';
import { mockLintResponse, mockCiYml } from '../mock_data'; import { mockLintResponse, mockCiYml } from '../mock_data';
...@@ -15,6 +16,7 @@ describe('Pipeline editor tabs component', () => { ...@@ -15,6 +16,7 @@ describe('Pipeline editor tabs component', () => {
const mockProvide = { const mockProvide = {
glFeatures: { glFeatures: {
ciConfigVisualizationTab: true, ciConfigVisualizationTab: true,
ciConfigMergedTab: true,
}, },
}; };
...@@ -35,72 +37,102 @@ describe('Pipeline editor tabs component', () => { ...@@ -35,72 +37,102 @@ describe('Pipeline editor tabs component', () => {
const findEditorTab = () => wrapper.find('[data-testid="editor-tab"]'); const findEditorTab = () => wrapper.find('[data-testid="editor-tab"]');
const findLintTab = () => wrapper.find('[data-testid="lint-tab"]'); const findLintTab = () => wrapper.find('[data-testid="lint-tab"]');
const findMergedTab = () => wrapper.find('[data-testid="merged-tab"]');
const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]'); const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]');
const findAlert = () => wrapper.findComponent(GlAlert);
const findCiLint = () => wrapper.findComponent(CiLint); const findCiLint = () => wrapper.findComponent(CiLint);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findPipelineGraph = () => wrapper.findComponent(PipelineGraph); const findPipelineGraph = () => wrapper.findComponent(PipelineGraph);
const findTextEditor = () => wrapper.findComponent(MockTextEditor); const findTextEditor = () => wrapper.findComponent(MockTextEditor);
const findMergedPreview = () => wrapper.findComponent(CiConfigMergedPreview);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
}); });
describe('tabs', () => { describe('editor tab', () => {
describe('editor tab', () => { it('displays editor only after the tab is mounted', async () => {
it('displays editor only after the tab is mounted', async () => { createComponent({ mountFn: mount });
createComponent({ mountFn: mount });
expect(findTextEditor().exists()).toBe(false); expect(findTextEditor().exists()).toBe(false);
await nextTick(); await nextTick();
expect(findTextEditor().exists()).toBe(true); expect(findTextEditor().exists()).toBe(true);
expect(findEditorTab().exists()).toBe(true); expect(findEditorTab().exists()).toBe(true);
});
}); });
});
describe('visualization tab', () => { describe('visualization tab', () => {
describe('with feature flag on', () => { describe('with feature flag on', () => {
describe('while loading', () => { describe('while loading', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ props: { isCiConfigDataLoading: true } }); createComponent({ props: { isCiConfigDataLoading: true } });
});
it('displays a loading icon if the lint query is loading', () => {
expect(findLoadingIcon().exists()).toBe(true);
expect(findPipelineGraph().exists()).toBe(false);
});
}); });
describe('after loading', () => {
beforeEach(() => { it('displays a loading icon if the lint query is loading', () => {
createComponent(); expect(findLoadingIcon().exists()).toBe(true);
}); expect(findPipelineGraph().exists()).toBe(false);
it('display the tab and visualization', () => {
expect(findVisualizationTab().exists()).toBe(true);
expect(findPipelineGraph().exists()).toBe(true);
});
}); });
}); });
describe('after loading', () => {
describe('with feature flag off', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent();
provide: {
glFeatures: { ciConfigVisualizationTab: false },
},
});
}); });
it('does not display the tab or component', () => { it('display the tab and visualization', () => {
expect(findVisualizationTab().exists()).toBe(false); expect(findVisualizationTab().exists()).toBe(true);
expect(findPipelineGraph().exists()).toBe(false); expect(findPipelineGraph().exists()).toBe(true);
}); });
}); });
}); });
describe('lint tab', () => { describe('with feature flag off', () => {
beforeEach(() => {
createComponent({
provide: {
glFeatures: { ciConfigVisualizationTab: false },
},
});
});
it('does not display the tab or component', () => {
expect(findVisualizationTab().exists()).toBe(false);
expect(findPipelineGraph().exists()).toBe(false);
});
});
});
describe('lint tab', () => {
describe('while loading', () => {
beforeEach(() => {
createComponent({ props: { isCiConfigDataLoading: true } });
});
it('displays a loading icon if the lint query is loading', () => {
expect(findLoadingIcon().exists()).toBe(true);
});
it('does not display the lint component', () => {
expect(findCiLint().exists()).toBe(false);
});
});
describe('after loading', () => {
beforeEach(() => {
createComponent();
});
it('display the tab and the lint component', () => {
expect(findLintTab().exists()).toBe(true);
expect(findCiLint().exists()).toBe(true);
});
});
});
describe('merged tab', () => {
describe('with feature flag on', () => {
describe('while loading', () => { describe('while loading', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ props: { isCiConfigDataLoading: true } }); createComponent({ props: { isCiConfigDataLoading: true } });
...@@ -109,21 +141,43 @@ describe('Pipeline editor tabs component', () => { ...@@ -109,21 +141,43 @@ describe('Pipeline editor tabs component', () => {
it('displays a loading icon if the lint query is loading', () => { it('displays a loading icon if the lint query is loading', () => {
expect(findLoadingIcon().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(true);
}); });
});
describe('when `mergedYaml` is undefined', () => {
beforeEach(() => {
createComponent({ props: { ciConfigData: {} } });
});
it('show an error message', () => {
expect(findAlert().exists()).toBe(true);
expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts.loadMergedYaml);
});
it('does not display the lint component', () => { it('does not render the `meged_preview` component', () => {
expect(findCiLint().exists()).toBe(false); expect(findMergedPreview().exists()).toBe(false);
}); });
}); });
describe('after loading', () => { describe('after loading', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
}); });
it('display the tab and the lint component', () => { it('display the tab and the merged preview component', () => {
expect(findLintTab().exists()).toBe(true); expect(findMergedTab().exists()).toBe(true);
expect(findCiLint().exists()).toBe(true); expect(findMergedPreview().exists()).toBe(true);
}); });
}); });
}); });
describe('with feature flag off', () => {
beforeEach(() => {
createComponent({ provide: { glFeatures: { ciConfigMergedTab: false } } });
});
it('does not display the merged tab', () => {
expect(findMergedTab().exists()).toBe(false);
expect(findMergedPreview().exists()).toBe(false);
});
});
}); });
}); });
...@@ -54,6 +54,7 @@ export const mockCiConfigQueryResponse = { ...@@ -54,6 +54,7 @@ export const mockCiConfigQueryResponse = {
data: { data: {
ciConfig: { ciConfig: {
errors: [], errors: [],
mergedYaml: mockCiYml,
status: CI_CONFIG_STATUS_VALID, status: CI_CONFIG_STATUS_VALID,
stages: { stages: {
__typename: 'CiConfigStageConnection', __typename: 'CiConfigStageConnection',
...@@ -139,6 +140,8 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => { ...@@ -139,6 +140,8 @@ export const mergeUnwrappedCiConfig = (mergedConfig) => {
export const mockLintResponse = { export const mockLintResponse = {
valid: true, valid: true,
mergedYaml: mockCiYml,
status: CI_CONFIG_STATUS_VALID,
errors: [], errors: [],
warnings: [], warnings: [],
jobs: [ jobs: [
......
...@@ -3,7 +3,7 @@ import { GlAlert, GlButton, GlLoadingIcon, GlTabs } from '@gitlab/ui'; ...@@ -3,7 +3,7 @@ import { GlAlert, GlButton, GlLoadingIcon, GlTabs } from '@gitlab/ui';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import TextEditor from '~/pipeline_editor/components/text_editor.vue'; import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
......
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue'; import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue'; import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue'; import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue';
import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue'; import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue';
import { MERGED_TAB, VISUALIZE_TAB } from '~/pipeline_editor/constants';
import { mockLintResponse, mockCiYml } from './mock_data'; import { mockLintResponse, mockCiYml } from './mock_data';
...@@ -21,9 +23,9 @@ describe('Pipeline editor home wrapper', () => { ...@@ -21,9 +23,9 @@ describe('Pipeline editor home wrapper', () => {
}); });
}; };
const findPipelineEditorHeader = () => wrapper.findComponent(PipelineEditorTabs); const findPipelineEditorHeader = () => wrapper.findComponent(PipelineEditorHeader);
const findPipelineEditorTabs = () => wrapper.findComponent(CommitSection); const findPipelineEditorTabs = () => wrapper.findComponent(PipelineEditorTabs);
const findCommitSection = () => wrapper.findComponent(PipelineEditorHeader); const findCommitSection = () => wrapper.findComponent(CommitSection);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -43,7 +45,33 @@ describe('Pipeline editor home wrapper', () => { ...@@ -43,7 +45,33 @@ describe('Pipeline editor home wrapper', () => {
expect(findPipelineEditorTabs().exists()).toBe(true); expect(findPipelineEditorTabs().exists()).toBe(true);
}); });
it('shows the commit section', () => { it('shows the commit section by default', () => {
expect(findCommitSection().exists()).toBe(true);
});
});
describe('commit form toggle', () => {
beforeEach(() => {
createComponent();
});
it('hides the commit form when in the merged tab', async () => {
expect(findCommitSection().exists()).toBe(true);
findPipelineEditorTabs().vm.$emit('set-current-tab', MERGED_TAB);
await nextTick();
expect(findCommitSection().exists()).toBe(false);
});
it('shows the form again when leaving the merged tab', async () => {
expect(findCommitSection().exists()).toBe(true);
findPipelineEditorTabs().vm.$emit('set-current-tab', MERGED_TAB);
await nextTick();
expect(findCommitSection().exists()).toBe(false);
findPipelineEditorTabs().vm.$emit('set-current-tab', VISUALIZE_TAB);
await nextTick();
expect(findCommitSection().exists()).toBe(true); expect(findCommitSection().exists()).toBe(true);
}); });
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment