Skip to content
Snippets Groups Projects
Commit 7148c32b authored by Miguel Rincon's avatar Miguel Rincon Committed by Robert Hunt
Browse files

Adds feedback banner on runner admin page

We are making layout improvements to the admin runners view
and want to collect feedback from users about these changes.

We are adding a banner to link to an issue to leave feedback.
parent 0f6fce38
No related branches found
No related tags found
1 merge request!96232Adds feedback banner on runner admin page
......@@ -15,6 +15,7 @@ import allRunnersQuery from 'ee_else_ce/runner/graphql/list/all_runners.query.gr
import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_count.query.graphql';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerStackedLayoutBanner from '../components/runner_stacked_layout_banner.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerBulkDelete from '../components/runner_bulk_delete.vue';
import RunnerBulkDeleteCheckbox from '../components/runner_bulk_delete_checkbox.vue';
......@@ -37,6 +38,7 @@ export default {
components: {
GlLink,
RegistrationDropdown,
RunnerStackedLayoutBanner,
RunnerFilteredSearchBar,
RunnerBulkDelete,
RunnerBulkDeleteCheckbox,
......@@ -169,6 +171,8 @@ export default {
</script>
<template>
<div>
<runner-stacked-layout-banner />
<div
class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0"
>
......
<script>
import allChangesCommittedSvg from '@gitlab/svgs/dist/illustrations/multi-editor_all_changes_committed_empty.svg';
import { GlBanner } from '@gitlab/ui';
import { s__ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const I18N_TITLE = s__("Runners|We've made some changes and want your feedback");
const I18N_DESCRIPTION = s__(
"Runners|We want you to be able to manage your runners easily and efficiently from this page, and we are making changes to get there. Give us feedback on how we're doing!",
);
const I18N_LINK = s__('Runners|Add your feedback in the issue');
// use a data url instead getting it from via HTML data-* attributes to simplify removal of this feature flag
const ILLUSTRATION_URL = `data:image/svg+xml;utf8,${encodeURIComponent(allChangesCommittedSvg)}`;
const ISSUE_URL = 'https://gitlab.com/gitlab-org/gitlab/-/issues/371621';
const STORAGE_KEY = 'runner_list_stacked_layout_feedback_dismissed';
export default {
components: {
GlBanner,
LocalStorageSync,
},
mixins: [glFeatureFlagMixin()],
data() {
return {
isDismissed: false,
};
},
computed: {
stackedLayoutEnabled() {
// Two feature flags can be used: runner_list_stacked_layout_admin or runner_list_stacked_layout
// Rollout issue: https://gitlab.com/gitlab-org/gitlab/-/issues/371031
const { runnerListStackedLayoutAdmin, runnerListStackedLayout } = this.glFeatures || {};
return runnerListStackedLayoutAdmin || runnerListStackedLayout;
},
showBanner() {
return this.stackedLayoutEnabled && !this.isDismissed;
},
},
methods: {
onClose() {
this.isDismissed = true;
},
},
I18N_TITLE,
I18N_DESCRIPTION,
I18N_LINK,
ILLUSTRATION_URL,
ISSUE_URL,
STORAGE_KEY,
};
</script>
<template>
<div>
<local-storage-sync v-model="isDismissed" :storage-key="$options.STORAGE_KEY" />
<gl-banner
v-if="showBanner"
:svg-path="$options.ILLUSTRATION_URL"
:title="$options.I18N_TITLE"
:button-text="$options.I18N_LINK"
:button-link="$options.ISSUE_URL"
class="gl-my-5"
@close="onClose"
>
<p>{{ $options.I18N_DESCRIPTION }}</p>
</gl-banner>
</div>
</template>
......@@ -13,6 +13,7 @@ import {
import groupRunnersQuery from 'ee_else_ce/runner/graphql/list/group_runners.query.graphql';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerStackedLayoutBanner from '../components/runner_stacked_layout_banner.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerListEmptyState from '../components/runner_list_empty_state.vue';
......@@ -37,6 +38,7 @@ export default {
components: {
GlLink,
RegistrationDropdown,
RunnerStackedLayoutBanner,
RunnerFilteredSearchBar,
RunnerList,
RunnerListEmptyState,
......@@ -179,6 +181,8 @@ export default {
<template>
<div>
<runner-stacked-layout-banner />
<div class="gl-display-flex gl-align-items-center">
<runner-type-tabs
ref="runner-type-tabs"
......
......@@ -33795,6 +33795,9 @@ msgstr ""
msgid "Runners|Add notes, like who owns the runner or what it should be used for."
msgstr ""
 
msgid "Runners|Add your feedback in the issue"
msgstr ""
msgid "Runners|All"
msgstr ""
 
......@@ -34230,6 +34233,12 @@ msgstr ""
msgid "Runners|View installation instructions"
msgstr ""
 
msgid "Runners|We want you to be able to manage your runners easily and efficiently from this page, and we are making changes to get there. Give us feedback on how we're doing!"
msgstr ""
msgid "Runners|We've made some changes and want your feedback"
msgstr ""
msgid "Runners|Windows 2019 Shell with manual scaling and optional scheduling. %{percentage} spot."
msgstr ""
 
......@@ -17,6 +17,7 @@ import { updateHistory } from '~/lib/utils/url_utility';
import { upgradeStatusTokenConfig } from 'ee_else_ce/runner/components/search_tokens/upgrade_status_token_config';
import { createLocalState } from '~/runner/graphql/list/local_state';
import AdminRunnersApp from '~/runner/admin_runners/admin_runners_app.vue';
import RunnerStackedLayoutBanner from '~/runner/components/runner_stacked_layout_banner.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerBulkDelete from '~/runner/components/runner_bulk_delete.vue';
......@@ -80,6 +81,7 @@ describe('AdminRunnersApp', () => {
let localMutations;
let showToast;
const findRunnerStackedLayoutBanner = () => wrapper.findComponent(RunnerStackedLayoutBanner);
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
......@@ -139,6 +141,11 @@ describe('AdminRunnersApp', () => {
wrapper.destroy();
});
it('shows the feedback banner', () => {
createComponent();
expect(findRunnerStackedLayoutBanner().exists()).toBe(true);
});
it('shows the runner setup instructions', () => {
createComponent();
......
import Vue from 'vue';
import { GlBanner } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerStackedLayoutBanner from '~/runner/components/runner_stacked_layout_banner.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
describe('RunnerStackedLayoutBanner', () => {
let wrapper;
const findBanner = () => wrapper.findComponent(GlBanner);
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const createComponent = ({ ...options } = {}, mountFn = shallowMount) => {
wrapper = mountFn(RunnerStackedLayoutBanner, {
...options,
});
};
it('Does not display a banner', () => {
createComponent();
expect(findBanner().exists()).toBe(false);
});
describe.each`
glFeatures
${{ runnerListStackedLayoutAdmin: true }}
${{ runnerListStackedLayout: true }}
`('When glFeatures = $glFeatures', ({ glFeatures }) => {
beforeEach(() => {
createComponent({
provide: {
glFeatures,
},
});
});
it('Displays a banner', () => {
expect(findBanner().props()).toMatchObject({
svgPath: expect.stringContaining('data:image/svg+xml;utf8,'),
title: expect.any(String),
buttonText: expect.any(String),
buttonLink: expect.stringContaining('https://gitlab.com/gitlab-org/gitlab/-/issues/'),
});
expect(findLocalStorageSync().exists()).toBe(true);
});
it('Does not display a banner when dismissed', async () => {
findLocalStorageSync().vm.$emit('input', true);
await Vue.nextTick();
expect(findBanner().exists()).toBe(false);
expect(findLocalStorageSync().exists()).toBe(true); // continues syncing after removal
});
});
});
......@@ -15,6 +15,7 @@ import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
import { upgradeStatusTokenConfig } from 'ee_else_ce/runner/components/search_tokens/upgrade_status_token_config';
import RunnerStackedLayoutBanner from '~/runner/components/runner_stacked_layout_banner.vue';
import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
......@@ -74,6 +75,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
describe('GroupRunnersApp', () => {
let wrapper;
const findRunnerStackedLayoutBanner = () => wrapper.findComponent(RunnerStackedLayoutBanner);
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
......@@ -122,6 +124,11 @@ describe('GroupRunnersApp', () => {
wrapper.destroy();
});
it('shows the feedback banner', () => {
createComponent();
expect(findRunnerStackedLayoutBanner().exists()).toBe(true);
});
it('shows the runner tabs with a runner count for each type', async () => {
await createComponent({ mountFn: mountExtended });
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment