Commit b5835ba5 authored by Mireya Andres's avatar Mireya Andres Committed by Peter Hegman

Use gl-drawer for pipeline editor help drawer

This aligns the behavior and styling of the drawer with
GitLab's standards. The drawer can be opened using the
help button in the pipeline editor.

Changelog: changed
parent 6f55625a
<script>
import { GlCard, GlLink, GlSprintf } from '@gitlab/ui';
import { GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
......@@ -22,7 +22,6 @@ export default {
),
},
components: {
GlCard,
GlLink,
GlSprintf,
},
......@@ -30,9 +29,8 @@ export default {
};
</script>
<template>
<gl-card>
<template #default>
<h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4>
<div>
<h3 class="gl-font-lg gl-mt-0 gl-mb-5">{{ $options.i18n.title }}</h3>
<p class="gl-mb-3">{{ $options.i18n.firstParagraph }}</p>
<ol class="gl-mb-3">
<li v-for="(item, i) in $options.i18n.listItems" :key="`li-${i}`">{{ item }}</li>
......@@ -46,6 +44,5 @@ export default {
</template>
</gl-sprintf>
</p>
</template>
</gl-card>
</div>
</template>
<script>
import { GlCard, GlSprintf } from '@gitlab/ui';
import { GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
......@@ -13,15 +13,13 @@ export default {
),
},
components: {
GlCard,
GlSprintf,
},
};
</script>
<template>
<gl-card>
<template #default>
<h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4>
<div>
<h3 class="gl-font-lg gl-mt-0 gl-mb-5">{{ $options.i18n.title }}</h3>
<p class="gl-mb-3">{{ $options.i18n.firstParagraph }}</p>
<p class="gl-mb-0">
<gl-sprintf :message="$options.i18n.secondParagraph">
......@@ -30,6 +28,5 @@ export default {
</template>
</gl-sprintf>
</p>
</template>
</gl-card>
</div>
</template>
<script>
import { GlCard, GlLink, GlSprintf } from '@gitlab/ui';
import { GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
......@@ -20,7 +20,6 @@ export default {
),
},
components: {
GlCard,
GlLink,
GlSprintf,
},
......@@ -28,9 +27,8 @@ export default {
};
</script>
<template>
<gl-card>
<template #default>
<h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4>
<div>
<h3 class="gl-font-lg gl-mt-0 gl-mb-5">{{ $options.i18n.title }}</h3>
<p class="gl-mb-3">{{ $options.i18n.firstParagraph }}</p>
<ul>
<li>
......@@ -70,6 +68,5 @@ export default {
</gl-sprintf>
</li>
</ul>
</template>
</gl-card>
</div>
</template>
<script>
import { GlCard } from '@gitlab/ui';
import { s__ } from '~/locale';
export default {
......@@ -9,16 +8,11 @@ export default {
'PipelineEditorTutorial|Use the Visualize and Lint tabs in the Pipeline Editor to visualize your pipeline and check for any errors or warnings before committing your changes.',
),
},
components: {
GlCard,
},
};
</script>
<template>
<gl-card>
<template #default>
<h4 class="gl-font-lg gl-mt-0">{{ $options.i18n.title }}</h4>
<div>
<h3 class="gl-font-lg gl-mt-0 gl-mb-5">{{ $options.i18n.title }}</h3>
<p class="gl-mb-0">{{ $options.i18n.firstParagraph }}</p>
</template>
</gl-card>
</div>
</template>
<script>
import { GlButton, GlIcon } from '@gitlab/ui';
import { GlDrawer } from '@gitlab/ui';
import { __ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { DRAWER_EXPANDED_KEY } from '../../constants';
import FirstPipelineCard from './cards/first_pipeline_card.vue';
import GettingStartedCard from './cards/getting_started_card.vue';
import PipelineConfigReferenceCard from './cards/pipeline_config_reference_card.vue';
import VisualizeAndLintCard from './cards/visualize_and_lint_card.vue';
const DRAWER_CARD_STYLES = ['gl-border-bottom-0', 'gl-pt-6!', 'gl-pb-0!', 'gl-line-height-20'];
export default {
width: {
expanded: '482px',
collapsed: '58px',
},
DRAWER_CARD_STYLES,
i18n: {
toggleTxt: __('Collapse'),
title: __('Help'),
},
localDrawerKey: DRAWER_EXPANDED_KEY,
components: {
FirstPipelineCard,
GettingStartedCard,
GlButton,
GlIcon,
LocalStorageSync,
GlDrawer,
PipelineConfigReferenceCard,
VisualizeAndLintCard,
},
data() {
return {
isExpanded: false,
topPosition: 0,
};
props: {
isVisible: {
type: Boolean,
required: false,
default: false,
},
computed: {
buttonIconName() {
return this.isExpanded ? 'chevron-double-lg-right' : 'chevron-double-lg-left';
},
buttonClass() {
return this.isExpanded ? 'gl-justify-content-end!' : '';
},
rootStyle() {
const { expanded, collapsed } = this.$options.width;
const top = this.topPosition;
const style = { top: `${top}px` };
return this.isExpanded ? { ...style, width: expanded } : { ...style, width: collapsed };
computed: {
drawerCardStyles() {
return '';
},
drawerHeightOffset() {
const wrapperEl = document.querySelector('.content-wrapper');
return wrapperEl ? `${wrapperEl.offsetTop}px` : '';
},
mounted() {
this.setTopPosition();
},
methods: {
setTopPosition() {
const navbarEl = document.querySelector('.js-navbar');
if (navbarEl) {
this.topPosition = navbarEl.getBoundingClientRect().bottom;
}
},
toggleDrawer() {
this.isExpanded = !this.isExpanded;
closeDrawer() {
this.$emit('close-drawer');
},
},
};
</script>
<template>
<local-storage-sync v-model="isExpanded" :storage-key="$options.localDrawerKey" as-json>
<aside
aria-live="polite"
class="gl-fixed gl-right-0 gl-bg-gray-10 gl-shadow-drawer gl-transition-property-width gl-transition-duration-medium gl-border-l-solid gl-border-1 gl-border-gray-100 gl-h-full gl-z-index-200 gl-overflow-y-auto"
:style="rootStyle"
>
<gl-button
category="tertiary"
class="gl-w-full gl-h-9 gl-rounded-0! gl-border-none! gl-border-b-solid! gl-border-1! gl-border-gray-100 gl-text-decoration-none! gl-outline-0! gl-display-flex"
:class="buttonClass"
:title="__('Toggle sidebar')"
data-qa-selector="toggle_sidebar_collapse_button"
@click="toggleDrawer"
>
<span v-if="isExpanded" class="gl-text-gray-500 gl-mr-3" data-testid="collapse-text">
{{ __('Collapse') }}
</span>
<gl-icon data-testid="toggle-icon" :name="buttonIconName" />
</gl-button>
<div
v-if="isExpanded"
class="gl-h-full gl-p-5"
data-testid="drawer-content"
data-qa-selector="drawer_content"
<gl-drawer
:header-height="drawerHeightOffset"
:open="isVisible"
:z-index="200"
@close="closeDrawer"
>
<getting-started-card class="gl-mb-4" />
<first-pipeline-card class="gl-mb-4" />
<visualize-and-lint-card class="gl-mb-4" />
<pipeline-config-reference-card />
<div class="gl-h-13"></div>
</div>
</aside>
</local-storage-sync>
<template #title>
<h2 class="gl-m-0 gl-font-lg">{{ $options.i18n.title }}</h2>
</template>
<getting-started-card :class="$options.DRAWER_CARD_STYLES" />
<first-pipeline-card :class="$options.DRAWER_CARD_STYLES" />
<visualize-and-lint-card :class="$options.DRAWER_CARD_STYLES" />
<pipeline-config-reference-card :class="$options.DRAWER_CARD_STYLES" />
</gl-drawer>
</template>
......@@ -7,13 +7,23 @@ import { pipelineEditorTrackingOptions, TEMPLATE_REPOSITORY_URL } from '../../co
export default {
i18n: {
browseTemplates: __('Browse templates'),
help: __('Help'),
},
TEMPLATE_REPOSITORY_URL,
components: {
GlButton,
},
mixins: [Tracking.mixin()],
props: {
showDrawer: {
type: Boolean,
required: true,
},
},
methods: {
toggleDrawer() {
this.$emit(this.showDrawer ? 'close-drawer' : 'open-drawer');
},
trackTemplateBrowsing() {
const { label, actions } = pipelineEditorTrackingOptions;
......@@ -30,9 +40,20 @@ export default {
size="small"
icon="external-link"
target="_blank"
data-testid="template-repo-link"
data-qa-selector="template_repo_link"
@click="trackTemplateBrowsing"
>
{{ $options.i18n.browseTemplates }}
</gl-button>
<gl-button
icon="information-o"
size="small"
data-testid="drawer-toggle"
data-qa-selector="drawer_toggle"
@click="toggleDrawer"
>
{{ $options.i18n.help }}
</gl-button>
</div>
</template>
......@@ -86,6 +86,10 @@ export default {
type: Boolean,
required: true,
},
showDrawer: {
type: Boolean,
required: true,
},
},
apollo: {
appStatus: {
......@@ -157,7 +161,7 @@ export default {
@click="setCurrentTab($options.tabConstants.CREATE_TAB)"
>
<walkthrough-popover v-if="isNewCiConfigFile" v-on="$listeners" />
<ci-editor-header />
<ci-editor-header :show-drawer="showDrawer" v-on="$listeners" />
<text-editor :commit-sha="commitSha" :value="ciFileContent" v-on="$listeners" />
</editor-tab>
<editor-tab
......
......@@ -45,8 +45,6 @@ export const TAB_QUERY_PARAM = 'tab';
export const COMMIT_ACTION_CREATE = 'CREATE';
export const COMMIT_ACTION_UPDATE = 'UPDATE';
export const DRAWER_EXPANDED_KEY = 'pipeline_editor_drawer_expanded';
export const BRANCH_PAGINATION_LIMIT = 20;
export const BRANCH_SEARCH_DEBOUNCE = '500';
export const SOURCE_EDITOR_DEBOUNCE = 500;
......
......@@ -388,7 +388,7 @@ export default {
@createEmptyConfigFile="setNewEmptyCiConfigFile"
@refetchContent="refetchContent"
/>
<div v-else class="gl-pr-10">
<div v-else>
<pipeline-editor-messages
:failure-type="failureType"
:failure-reasons="failureReasons"
......
......@@ -60,6 +60,7 @@ export default {
currentTab: CREATE_TAB,
scrollToCommitForm: false,
shouldLoadNewBranch: false,
showDrawer: false,
showSwitchBranchModal: false,
};
},
......@@ -72,9 +73,15 @@ export default {
closeBranchModal() {
this.showSwitchBranchModal = false;
},
closeDrawer() {
this.showDrawer = false;
},
handleConfirmSwitchBranch() {
this.showSwitchBranchModal = true;
},
openDrawer() {
this.showDrawer = true;
},
switchBranch() {
this.showSwitchBranchModal = false;
this.shouldLoadNewBranch = true;
......@@ -122,7 +129,10 @@ export default {
:ci-file-content="ciFileContent"
:commit-sha="commitSha"
:is-new-ci-config-file="isNewCiConfigFile"
:show-drawer="showDrawer"
v-on="$listeners"
@open-drawer="openDrawer"
@close-drawer="closeDrawer"
@set-current-tab="setCurrentTab"
@walkthrough-popover-cta-clicked="setScrollToCommitForm"
/>
......@@ -137,6 +147,10 @@ export default {
@scrolled-to-commit-form="setScrollToCommitForm(false)"
v-on="$listeners"
/>
<pipeline-editor-drawer />
<pipeline-editor-drawer
:is-visible="showDrawer"
v-on="$listeners"
@close-drawer="closeDrawer"
/>
</div>
</template>
......@@ -15,9 +15,9 @@ module QA
element :target_branch_field, required: true
end
view 'app/assets/javascripts/pipeline_editor/components/drawer/pipeline_editor_drawer.vue' do
element :toggle_sidebar_collapse_button
element :drawer_content
view 'app/assets/javascripts/pipeline_editor/components/editor/ci_editor_header.vue' do
element :drawer_toggle, required: true
element :template_repo_link, required: true
end
view 'app/assets/javascripts/vue_shared/components/source_editor.vue' do
......@@ -46,13 +46,6 @@ module QA
element :file_editor_container
end
def initialize
super
wait_for_requests
close_toggle_sidebar
end
def open_branch_selector_dropdown
click_element(:branch_selector_button)
end
......@@ -148,15 +141,6 @@ module QA
find('.nav-item', text: name).click
end
end
# If the page thinks user has never opened pipeline editor before
# It will expand pipeline editor sidebar by default
# Collapse the sidebar if it is expanded
def close_toggle_sidebar
return unless has_element?(:drawer_content)
click_element(:toggle_sidebar_collapse_button)
end
end
end
end
......
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import FirstPipelineCard from '~/pipeline_editor/components/drawer/cards/first_pipeline_card.vue';
import GettingStartedCard from '~/pipeline_editor/components/drawer/cards/getting_started_card.vue';
import PipelineConfigReferenceCard from '~/pipeline_editor/components/drawer/cards/pipeline_config_reference_card.vue';
import VisualizeAndLintCard from '~/pipeline_editor/components/drawer/cards/visualize_and_lint_card.vue';
import { GlDrawer } from '@gitlab/ui';
import PipelineEditorDrawer from '~/pipeline_editor/components/drawer/pipeline_editor_drawer.vue';
import { DRAWER_EXPANDED_KEY } from '~/pipeline_editor/constants';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
describe('Pipeline editor drawer', () => {
useLocalStorageSpy();
let wrapper;
const findDrawer = () => wrapper.findComponent(GlDrawer);
const createComponent = () => {
wrapper = shallowMount(PipelineEditorDrawer, {
stubs: { LocalStorageSync },
});
wrapper = shallowMount(PipelineEditorDrawer);
};
const findFirstPipelineCard = () => wrapper.findComponent(FirstPipelineCard);
const findGettingStartedCard = () => wrapper.findComponent(GettingStartedCard);
const findPipelineConfigReferenceCard = () => wrapper.findComponent(PipelineConfigReferenceCard);
const findToggleBtn = () => wrapper.findComponent(GlButton);
const findVisualizeAndLintCard = () => wrapper.findComponent(VisualizeAndLintCard);
const findArrowIcon = () => wrapper.find('[data-testid="toggle-icon"]');
const findCollapseText = () => wrapper.find('[data-testid="collapse-text"]');
const findDrawerContent = () => wrapper.find('[data-testid="drawer-content"]');
const clickToggleBtn = async () => findToggleBtn().vm.$emit('click');
const originalObjects = [];
beforeEach(() => {
originalObjects.push(window.gon, window.gl);
});
afterEach(() => {
wrapper.destroy();
localStorage.clear();
[window.gon, window.gl] = originalObjects;
});
describe('default expanded state', () => {
it('sets the drawer to be closed by default', async () => {
it('emits close event when closing the drawer', () => {
createComponent();
expect(findDrawerContent().exists()).toBe(false);
});
});
describe('when the drawer is collapsed', () => {
beforeEach(async () => {
createComponent();
});
expect(wrapper.emitted('close-drawer')).toBeUndefined();
it('shows the left facing arrow icon', () => {
expect(findArrowIcon().props('name')).toBe('chevron-double-lg-left');
});
findDrawer().vm.$emit('close');
it('does not show the collapse text', () => {
expect(findCollapseText().exists()).toBe(false);
});
it('does not show the drawer content', () => {
expect(findDrawerContent().exists()).toBe(false);
});
it('can open the drawer by clicking on the toggle button', async () => {
expect(findDrawerContent().exists()).toBe(false);
await clickToggleBtn();
expect(findDrawerContent().exists()).toBe(true);
});
});
describe('when the drawer is expanded', () => {
beforeEach(async () => {
createComponent();
await clickToggleBtn();
});
it('shows the right facing arrow icon', () => {
expect(findArrowIcon().props('name')).toBe('chevron-double-lg-right');
});
it('shows the collapse text', () => {
expect(findCollapseText().exists()).toBe(true);
});
it('shows the drawer content', () => {
expect(findDrawerContent().exists()).toBe(true);
});
it('shows all the introduction cards', () => {
expect(findFirstPipelineCard().exists()).toBe(true);
expect(findGettingStartedCard().exists()).toBe(true);
expect(findPipelineConfigReferenceCard().exists()).toBe(true);
expect(findVisualizeAndLintCard().exists()).toBe(true);
});
it('can close the drawer by clicking on the toggle button', async () => {
expect(findDrawerContent().exists()).toBe(true);
await clickToggleBtn();
expect(findDrawerContent().exists()).toBe(false);
});
});
describe('local storage', () => {
it('saves the drawer expanded value to local storage', async () => {
localStorage.setItem(DRAWER_EXPANDED_KEY, 'false');
createComponent();
await clickToggleBtn();
expect(localStorage.setItem.mock.calls).toEqual([
[DRAWER_EXPANDED_KEY, 'false'],
[DRAWER_EXPANDED_KEY, 'true'],
]);
});
it('loads the drawer collapsed when local storage is set to `false`, ', async () => {
localStorage.setItem(DRAWER_EXPANDED_KEY, false);
createComponent();
await nextTick();
expect(findDrawerContent().exists()).toBe(false);
});
it('loads the drawer expanded when local storage is set to `true`, ', async () => {
localStorage.setItem(DRAWER_EXPANDED_KEY, true);
createComponent();
await nextTick();
expect(findDrawerContent().exists()).toBe(true);
});
expect(wrapper.emitted('close-drawer')).toHaveLength(1);
});
});
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import CiEditorHeader from '~/pipeline_editor/components/editor/ci_editor_header.vue';
import {
......@@ -11,11 +11,18 @@ describe('CI Editor Header', () => {
let wrapper;
let trackingSpy = null;
const createComponent = () => {
wrapper = shallowMount(CiEditorHeader, {});
const createComponent = ({ showDrawer = false } = {}) => {
wrapper = extendedWrapper(
shallowMount(CiEditorHeader, {
propsData: {
showDrawer,
},
}),
);
};
const findLinkBtn = () => wrapper.findComponent(GlButton);
const findLinkBtn = () => wrapper.findByTestId('template-repo-link');
const findHelpBtn = () => wrapper.findByTestId('drawer-toggle');
afterEach(() => {
wrapper.destroy();
......@@ -50,4 +57,42 @@ describe('CI Editor Header', () => {
});
});
});
describe('help button', () => {
beforeEach(() => {
createComponent();
});
it('finds the help button', () => {
expect(findHelpBtn().exists()).toBe(true);
});
it('has the information-o icon', () => {
expect(findHelpBtn().props('icon')).toBe('information-o');
});
describe('when pipeline editor drawer is closed', () => {
it('emits open drawer event when clicked', () => {
createComponent({ showDrawer: false });
expect(wrapper.emitted('open-drawer')).toBeUndefined();
findHelpBtn().vm.$emit('click');
expect(wrapper.emitted('open-drawer')).toHaveLength(1);
});
});
describe('when pipeline editor drawer is open', () => {
it('emits close drawer event when clicked', () => {
createComponent({ showDrawer: true });
expect(wrapper.emitted('close-drawer')).toBeUndefined();
findHelpBtn().vm.$emit('click');
expect(wrapper.emitted('close-drawer')).toHaveLength(1);
});
});
});
});
......@@ -40,6 +40,7 @@ describe('Pipeline editor tabs component', () => {
ciConfigData: mockLintResponse,
ciFileContent: mockCiYml,
isNewCiConfigFile: true,
showDrawer: false,
...props,
},
data() {
......
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { GlModal } from '@gitlab/ui';
import { GlButton, GlDrawer, GlModal } from '@gitlab/ui';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import CiEditorHeader from '~/pipeline_editor/components/editor/ci_editor_header.vue';
import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue';
import PipelineEditorDrawer from '~/pipeline_editor/components/drawer/pipeline_editor_drawer.vue';
import PipelineEditorFileNav from '~/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue';
......@@ -18,7 +20,8 @@ describe('Pipeline editor home wrapper', () => {
let wrapper;
const createComponent = ({ props = {}, glFeatures = {}, data = {}, stubs = {} } = {}) => {
wrapper = shallowMount(PipelineEditorHome, {
wrapper = extendedWrapper(
shallowMount(PipelineEditorHome, {
data: () => data,
propsData: {
ciConfigData: mockLintResponse,
......@@ -35,7 +38,8 @@ describe('Pipeline editor home wrapper', () => {
},
},
stubs,
});
}),
);
};
const findBranchSwitcher = () => wrapper.findComponent(BranchSwitcher);
......@@ -45,6 +49,7 @@ describe('Pipeline editor home wrapper', () => {
const findPipelineEditorDrawer = () => wrapper.findComponent(PipelineEditorDrawer);
const findPipelineEditorHeader = () => wrapper.findComponent(PipelineEditorHeader);
const findPipelineEditorTabs = () => wrapper.findComponent(PipelineEditorTabs);
const findHelpBtn = () => wrapper.findByTestId('drawer-toggle');
afterEach(() => {
wrapper.destroy();
......@@ -70,10 +75,6 @@ describe('Pipeline editor home wrapper', () => {
it('shows the commit section by default', () => {
expect(findCommitSection().exists()).toBe(true);
});
it('show the pipeline drawer', () => {
expect(findPipelineEditorDrawer().exists()).toBe(true);
});
});
describe('modal when switching branch', () => {
......@@ -175,4 +176,58 @@ describe('Pipeline editor home wrapper', () => {
});
});
});
describe('help drawer', () => {
const clickHelpBtn = async () => {
findHelpBtn().vm.$emit('click');
await nextTick();
};
it('hides the drawer by default', () => {
createComponent();
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
});
it('toggles the drawer on button click', async () => {
createComponent({
stubs: {
CiEditorHeader,
GlButton,
GlDrawer,
PipelineEditorTabs,
PipelineEditorDrawer,
},
});
await clickHelpBtn();
expect(findPipelineEditorDrawer().props('isVisible')).toBe(true);
await clickHelpBtn();
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
});
it("closes the drawer through the drawer's close button", async () => {
createComponent({
stubs: {
CiEditorHeader,
GlButton,
GlDrawer,
PipelineEditorTabs,
PipelineEditorDrawer,
},
});
await clickHelpBtn();
expect(findPipelineEditorDrawer().props('isVisible')).toBe(true);
findPipelineEditorDrawer().find(GlDrawer).vm.$emit('close');
await nextTick();
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
});
});
});
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