Commit 0f586173 authored by Eulyeon Ko's avatar Eulyeon Ko

Use Vue issuables list for the service desk

Change issuables_list_app.vue to accept emptyStateMeta prop
that contains the attributes for use in gl-empty-state.

Add the Vue issuables list initializer to the service desk page.

Move the empty state logic for the service desk into js.

Add service desk helper to return empty state meta
parent 3615ad57
<script>
import { toNumber, omit } from 'lodash';
import { GlEmptyState, GlPagination, GlSkeletonLoading } from '@gitlab/ui';
import {
GlEmptyState,
GlPagination,
GlSkeletonLoading,
GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui';
import flash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import {
......@@ -23,9 +28,13 @@ import {
} from '../constants';
import { setUrlParams } from '~/lib/utils/url_utility';
import issueableEventHub from '../eventhub';
import { emptyStateHelper } from '../service_desk_helper';
export default {
LOADING_LIST_ITEMS_LENGTH,
directives: {
SafeHtml,
},
components: {
GlEmptyState,
GlPagination,
......@@ -39,15 +48,9 @@ export default {
required: false,
default: false,
},
createIssuePath: {
type: String,
required: false,
default: '',
},
emptySvgPath: {
type: String,
required: false,
default: '',
emptyStateMeta: {
type: Object,
required: true,
},
endpoint: {
type: String,
......@@ -94,28 +97,34 @@ export default {
emptyState() {
if (this.issuables.length) {
return {}; // Empty state shouldn't be shown here
} else if (this.isServiceDesk) {
return emptyStateHelper(this.emptyStateMeta);
} else if (this.hasFilters) {
return {
title: __('Sorry, your filter produced no results'),
svgPath: this.emptyStateMeta.svgPath,
description: __('To widen your search, change or remove filters above'),
primaryLink: this.createIssuePath,
primaryLink: this.emptyStateMeta.createIssuePath,
primaryText: __('New issue'),
};
} else if (this.filters.state === 'opened') {
return {
title: __('There are no open issues'),
svgPath: this.emptyStateMeta.svgPath,
description: __('To keep this project going, create a new issue'),
primaryLink: this.createIssuePath,
primaryLink: this.emptyStateMeta.createIssuePath,
primaryText: __('New issue'),
};
} else if (this.filters.state === 'closed') {
return {
title: __('There are no closed issues'),
svgPath: this.emptyStateMeta.svgPath,
};
}
return {
title: __('There are no issues to show'),
svgPath: this.emptyStateMeta.svgPath,
description: __(
'The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.',
),
......@@ -157,6 +166,9 @@ export default {
nextPage: this.paginationNext,
};
},
isServiceDesk() {
return this.type === 'service_desk';
},
isJira() {
return this.type === 'jira';
},
......@@ -394,10 +406,13 @@ export default {
<gl-empty-state
v-else
:title="emptyState.title"
:description="emptyState.description"
:svg-path="emptySvgPath"
:svg-path="emptyState.svgPath"
:primary-button-link="emptyState.primaryLink"
:primary-button-text="emptyState.primaryText"
/>
>
<template #description>
<div v-safe-html="emptyState.description"></div>
</template>
</gl-empty-state>
</div>
</template>
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import IssuableListRootApp from './components/issuable_list_root_app.vue';
import IssuablesListApp from './components/issuables_list_app.vue';
......@@ -41,7 +41,7 @@ function mountIssuablesListApp() {
}
document.querySelectorAll('.js-issuables-list').forEach(el => {
const { canBulkEdit, ...data } = el.dataset;
const { canBulkEdit, emptyStateMeta = {}, ...data } = el.dataset;
return new Vue({
el,
......@@ -49,6 +49,10 @@ function mountIssuablesListApp() {
return createElement(IssuablesListApp, {
props: {
...data,
emptyStateMeta:
Object.keys(emptyStateMeta).length !== 0
? convertObjectPropsToCamelCase(JSON.parse(emptyStateMeta))
: {},
canBulkEdit: Boolean(canBulkEdit),
},
});
......
import { __ } from '~/locale';
/**
* Returns the attributes used for gl-empty-state in the Service Desk issues list.
*/
export function emptyStateHelper(emptyStateMeta) {
const { isServiceDeskSupported, svgPath, serviceDeskHelpPage } = emptyStateMeta;
if (isServiceDeskSupported) {
const title = __(
'Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab',
);
const commonMessage = __(
'Those emails automatically become issues (with the comments becoming the email conversation) listed here.',
);
const commonDescription = `
<span>${commonMessage}</span>
<a href="${serviceDeskHelpPage}">${__('Read more')}</a>`;
if (emptyStateMeta.canEditProjectSettings && emptyStateMeta.isServiceDeskEnabled) {
return {
title,
svgPath,
description: `<p>${__('Have your users email')} <code>${
emptyStateMeta.serviceDeskAddress
}</code></p> ${commonDescription}`,
};
}
if (emptyStateMeta.canEditProjectSettings && !emptyStateMeta.isServiceDeskEnabled) {
return {
title,
svgPath,
description: commonDescription,
primaryLink: emptyStateMeta.editProjectPage,
primaryText: __('Turn on Service Desk'),
};
}
return {
title,
svgPath,
description: commonDescription,
};
}
return {
title: __('Service Desk is enabled but not yet active'),
svgPath,
description: __('You must set up incoming email before it becomes active.'),
primaryLink: emptyStateMeta.incomingEmailHelpPage,
primaryText: __('More information'),
};
}
export default {};
import FilteredSearchServiceDesk from './filtered_search';
import initIssuablesList from '~/issuables_list';
document.addEventListener('DOMContentLoaded', () => {
const supportBotData = JSON.parse(
document.querySelector('.js-service-desk-issues').dataset.supportBot,
);
const filteredSearchManager = new FilteredSearchServiceDesk(supportBotData);
if (document.querySelector('.filtered-search')) {
const filteredSearchManager = new FilteredSearchServiceDesk(supportBotData);
filteredSearchManager.setup();
}
filteredSearchManager.setup();
if (gon.features?.vueIssuablesList) {
initIssuablesList();
}
});
# frozen_string_literal: true
module Projects::Issues::ServiceDeskHelper
def service_desk_meta(project)
empty_state_meta = {
is_service_desk_supported: Gitlab::ServiceDesk.supported?,
is_service_desk_enabled: project.service_desk_enabled?,
can_edit_project_settings: can?(current_user, :admin_project, project)
}
if Gitlab::ServiceDesk.supported?
empty_state_meta.merge(supported_meta(project))
else
empty_state_meta.merge(unsupported_meta(project))
end
end
private
def supported_meta(project)
{
service_desk_address: project.service_desk_address,
service_desk_help_page: expose_url('help/user/project/service_desk'),
edit_project_page: expose_url(edit_project_path(project)),
svg_path: image_path('illustrations/service_desk_empty.svg')
}
end
def unsupported_meta(project)
{
incoming_email_help_page: "#{expose_url('/help/administration/incoming_email')}\#set-it-up",
svg_path: image_path('illustrations/service-desk-setup.svg')
}
end
end
......@@ -25,7 +25,7 @@
- if Feature.enabled?(:vue_issuables_list, @group)
.js-issuables-list{ data: { endpoint: expose_url(api_v4_groups_issues_path(id: @group.id)),
'can-bulk-edit': @can_bulk_update.to_json,
'empty-svg-path': image_path('illustrations/issues.svg'),
'empty-state-meta': { svg_path: image_path('illustrations/issues.svg') },
'sort-key': @sort } }
- else
= render 'shared/issues'
- if Feature.enabled?(:vue_issuables_list, @project)
.js-issuables-list{ data: { endpoint: expose_url(api_v4_projects_issues_path(id: @project.id)),
'create_issue_path': expose_url(new_project_issue_path(@project)),
- data_endpoint = local_assigns.fetch(:data_endpoint, expose_url(api_v4_projects_issues_path(id: @project.id)))
- default_empty_state_meta = { create_issue_path: expose_url(new_project_issue_path(@project)), svg_path: image_path('illustrations/issues.svg') }
- data_empty_state_meta = local_assigns.fetch(:data_empty_state_meta, default_empty_state_meta)
- type = local_assigns.fetch(:type, '')
.js-issuables-list{ data: { endpoint: data_endpoint,
'empty-state-meta': data_empty_state_meta.to_json,
'can-bulk-edit': @can_bulk_update.to_json,
'empty-svg-path': image_path('illustrations/issues.svg'),
'sort-key': @sort } }
'sort-key': @sort,
'type': type } }
- else
- empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues')
%ul.content-list.issues-list.issuable-list{ class: ("manual-ordering" if @sort == 'relative_position') }
......
- service_desk_enabled = @project.service_desk_enabled?
- can_edit_project_settings = can?(current_user, :admin_project, @project)
- title_text = _("Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab")
- if Gitlab::ServiceDesk.supported?
.empty-state
.svg-content
= render 'shared/empty_states/icons/service_desk_empty_state.svg'
.text-content
%h4= title_text
- if can_edit_project_settings && service_desk_enabled
%p
= _("Have your users email")
%code= @project.service_desk_address
%span= _("Those emails automatically become issues (with the comments becoming the email conversation) listed here.")
= link_to _('Read more'), help_page_path('user/project/service_desk')
- if can_edit_project_settings && !service_desk_enabled
.text-center
= link_to _("Turn on Service Desk"), edit_project_path(@project), class: 'btn btn-success'
- else
.empty-state
.svg-content
= render 'shared/empty_states/icons/service_desk_setup.svg'
.text-content
%h4= _('Service Desk is enabled but not yet active')
%p
= _("You must set up incoming email before it becomes active.")
= link_to _('More information'), help_page_path('administration/incoming_email', anchor: 'set-it-up')
- is_empty_state = @issues.blank?
- service_desk_enabled = @project.service_desk_enabled?
- callout_selector = is_empty_state ? 'empty-state' : 'non-empty-state media'
- svg_path = !is_empty_state ? 'shared/empty_states/icons/service_desk_callout.svg' : 'shared/empty_states/icons/service_desk_empty_state.svg'
- can_edit_project_settings = can?(current_user, :admin_project, @project)
- title_text = _("Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab")
- if Gitlab::ServiceDesk.supported?
%div{ class: "#{callout_selector}" }
.svg-content
= render svg_path
.non-empty-state.media
.svg-content
= render 'shared/empty_states/icons/service_desk_callout.svg'
%div{ class: is_empty_state ? "text-content" : "gl-mt-3 gl-ml-3" }
- if is_empty_state
%h4= title_text
- else
%h5= title_text
.gl-mt-3.gl-ml-3
%h5= title_text
- if can_edit_project_settings && service_desk_enabled
%p
= _("Have your users email")
%code= @project.service_desk_address
- if can_edit_project_settings && service_desk_enabled
%p
= _("Have your users email")
%code= @project.service_desk_address
%span= _("Those emails automatically become issues (with the comments becoming the email conversation) listed here.")
= link_to _('Read more'), help_page_path('user/project/service_desk')
%span= _("Those emails automatically become issues (with the comments becoming the email conversation) listed here.")
= link_to _('Read more'), help_page_path('user/project/service_desk')
- if can_edit_project_settings && !service_desk_enabled
%div{ class: is_empty_state ? "text-center" : "gl-mt-3" }
= link_to _("Turn on Service Desk"), edit_project_path(@project), class: 'btn btn-success'
- else
.empty-state
.svg-content
= render 'shared/empty_states/icons/service_desk_setup.svg'
.text-content
%h4= _('Service Desk is enabled but not yet active')
%p
= _("You must set up incoming email before it becomes active.")
= link_to _('More information'), help_page_path('administration/incoming_email', anchor: 'set-it-up')
- if can_edit_project_settings && !service_desk_enabled
.gl-mt-3
= link_to _("Turn on Service Desk"), edit_project_path(@project), class: 'btn btn-success'
......@@ -5,9 +5,11 @@
- content_for :breadcrumbs_extra do
= render "projects/issues/nav_btns", show_export_button: false, show_rss_button: false
- support_bot_attrs = UserSerializer.new.represent(User.support_bot).to_json
- support_bot_attrs = { service_desk_enabled: @project.service_desk_enabled?, **UserSerializer.new.represent(User.support_bot) }.to_json
%div{ class: "js-service-desk-issues service-desk-issues", data: { support_bot: support_bot_attrs } }
- data_endpoint = "#{expose_url(api_v4_projects_issues_path(id: @project.id))}?author_id=#{User.support_bot.id}"
%div{ class: "js-service-desk-issues service-desk-issues", data: { support_bot: support_bot_attrs, service_desk_meta: service_desk_meta(@project) } }
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls.d-block.d-sm-none
......@@ -15,7 +17,15 @@
- if @issues.present?
= render 'shared/issuable/search_bar', type: :issues
= render 'service_desk_info_content'
- if Gitlab::ServiceDesk.supported?
= render 'service_desk_info_content'
-# TODO Remove empty_state_path once vue_issuables_list FF is removed.
-# https://gitlab.com/gitlab-org/gitlab/-/issues/235652
-# `empty_state_path` is used to render the empty state in the HAML version of issuables list.
.issues-holder
= render 'projects/issues/issues', empty_state_path: 'service_desk_info_content'
= render 'projects/issues/issues',
empty_state_path: 'service_desk_empty_state',
data_endpoint: data_endpoint,
data_empty_state_meta: service_desk_meta(@project),
type: 'service_desk'
......@@ -6,7 +6,7 @@
.js-issuables-list{ data: { endpoint: expose_path(project_integrations_jira_issues_path(@project, format: :json)),
'can-bulk-edit': false,
'empty-svg-path': image_path('illustrations/issues.svg'),
'empty-state-meta': { svg_path: image_path('illustrations/issues.svg') },
'sort-key': @sort,
type: 'jira',
project_path: @project.full_path, } }
......@@ -7,8 +7,6 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
let(:user) { create(:user) }
before do
stub_feature_flags(vue_issuables_list: false)
allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
......@@ -39,7 +37,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
expect(page).to have_css('.empty-state')
expect(page).to have_text('Service Desk is enabled but not yet active')
expect(page).to have_text('You must set up incoming email before it becomes active')
expect(page).to have_link('More information', href: help_page_path('administration/incoming_email', anchor: 'set-it-up'))
expect(page).to have_link('More information', href: /#{help_page_path('administration/incoming_email', anchor: 'set-it-up')}/)
end
end
......@@ -56,7 +54,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
it 'displays the large info box, documentation, and a button to configure' do
aggregate_failures do
expect(page).to have_css('.empty-state')
expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
expect(page).to have_link('Read more', href: /#{help_page_path('user/project/service_desk')}/)
expect(page).to have_link('Turn on Service Desk')
end
end
......@@ -85,7 +83,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
it 'displays the large info box, documentation, and the address' do
aggregate_failures do
expect(page).to have_css('.empty-state')
expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
expect(page).to have_link('Read more', href: /#{help_page_path('user/project/service_desk')}/)
expect(page).not_to have_link('Turn on Service Desk')
expect(page).to have_content(project.service_desk_address)
end
......@@ -97,13 +95,16 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
project.add_guest(user_2)
sign_in(user_2)
wait_for_all_requests
visit service_desk_project_issues_path(project)
end
it 'displays the large info box and the documentation link' do
aggregate_failures do
expect(page).to have_css('.empty-state')
expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
expect(page).to have_link('Read more', href: /#{help_page_path('user/project/service_desk')}/)
expect(page).not_to have_link('Turn on Service Desk')
expect(page).not_to have_content(project.service_desk_address)
end
......@@ -126,7 +127,7 @@ RSpec.describe 'Service Desk Issue Tracker', :js do
it 'displays the small info box, documentation, a button to configure service desk, and the address' do
aggregate_failures do
expect(page).to have_css('.non-empty-state')
expect(page).to have_link('Read more', href: help_page_path('user/project/service_desk'))
expect(page).to have_link('Read more', href: /#{help_page_path('user/project/service_desk')}/)
expect(page).not_to have_link('Turn on Service Desk')
expect(page).to have_content(project.service_desk_address)
end
......
......@@ -2,7 +2,6 @@
exports[`Issuables list component with empty issues response with all state should display a catch-all if there are no issues to show 1`] = `
<gl-empty-state-stub
description="The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project."
svgpath="/emptySvg"
title="There are no issues to show"
/>
......
......@@ -21,7 +21,7 @@ jest.mock('~/lib/utils/common_utils', () => ({
const TEST_LOCATION = `${TEST_HOST}/issues`;
const TEST_ENDPOINT = '/issues';
const TEST_CREATE_ISSUES_PATH = '/createIssue';
const TEST_EMPTY_SVG_PATH = '/emptySvg';
const TEST_SVG_PATH = '/emptySvg';
const setUrl = query => {
window.location.href = `${TEST_LOCATION}${query}`;
......@@ -48,11 +48,15 @@ describe('Issuables list component', () => {
};
const factory = (props = { sortKey: 'priority' }) => {
const emptyStateMeta = {
createIssuePath: TEST_CREATE_ISSUES_PATH,
svgPath: TEST_SVG_PATH,
};
wrapper = shallowMount(IssuablesListApp, {
propsData: {
endpoint: TEST_ENDPOINT,
createIssuePath: TEST_CREATE_ISSUES_PATH,
emptySvgPath: TEST_EMPTY_SVG_PATH,
emptyStateMeta,
...props,
},
});
......@@ -117,9 +121,10 @@ describe('Issuables list component', () => {
expect(wrapper.vm).toMatchObject({
// Props
canBulkEdit: false,
createIssuePath: TEST_CREATE_ISSUES_PATH,
emptySvgPath: TEST_EMPTY_SVG_PATH,
emptyStateMeta: {
createIssuePath: TEST_CREATE_ISSUES_PATH,
svgPath: TEST_SVG_PATH,
},
// Data
filters: {
state: 'opened',
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Issues::ServiceDeskHelper do
let_it_be(:project) { create(:project, :public, service_desk_enabled: true) }
let(:user) { build_stubbed(:user) }
let(:current_user) { user }
describe '#service_desk_meta' do
subject { helper.service_desk_meta(project) }
context "when service desk is supported and user can edit project settings" do
before do
allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(true)
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).with(current_user, :admin_project, project).and_return(true)
end
it {
is_expected.to eq({
is_service_desk_supported: true,
is_service_desk_enabled: true,
can_edit_project_settings: true,
service_desk_address: project.service_desk_address,
service_desk_help_page: expose_url('help/user/project/service_desk'),
edit_project_page: expose_url(edit_project_path(project)),
svg_path: ActionController::Base.helpers.image_path('illustrations/service_desk_empty.svg')
})
}
end
context "when service desk is not supported and user cannot edit project settings" do
before do
allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(false)
allow(Gitlab::IncomingEmail).to receive(:supports_wildcard?).and_return(false)
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).with(current_user, :admin_project, project).and_return(false)
end
it {
is_expected.to eq({
is_service_desk_supported: false,
is_service_desk_enabled: false,
can_edit_project_settings: false,
incoming_email_help_page: "#{expose_url('/help/administration/incoming_email')}\#set-it-up",
svg_path: ActionController::Base.helpers.image_path('illustrations/service-desk-setup.svg')
})
}
end
end
end
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