Commit ec0d8e14 authored by Coung Ngo's avatar Coung Ngo Committed by Alex Pooley

Add new issue split dropdown to group issues list refactor

parent dab58c88
...@@ -82,6 +82,7 @@ import searchLabelsQuery from '../queries/search_labels.query.graphql'; ...@@ -82,6 +82,7 @@ import searchLabelsQuery from '../queries/search_labels.query.graphql';
import searchMilestonesQuery from '../queries/search_milestones.query.graphql'; import searchMilestonesQuery from '../queries/search_milestones.query.graphql';
import searchUsersQuery from '../queries/search_users.query.graphql'; import searchUsersQuery from '../queries/search_users.query.graphql';
import IssueCardTimeInfo from './issue_card_time_info.vue'; import IssueCardTimeInfo from './issue_card_time_info.vue';
import NewIssueDropdown from './new_issue_dropdown.vue';
export default { export default {
i18n, i18n,
...@@ -96,6 +97,7 @@ export default { ...@@ -96,6 +97,7 @@ export default {
IssuableByEmail, IssuableByEmail,
IssuableList, IssuableList,
IssueCardTimeInfo, IssueCardTimeInfo,
NewIssueDropdown,
BlockingIssuesCount: () => import('ee_component/issues/components/blocking_issues_count.vue'), BlockingIssuesCount: () => import('ee_component/issues/components/blocking_issues_count.vue'),
}, },
directives: { directives: {
...@@ -126,6 +128,9 @@ export default { ...@@ -126,6 +128,9 @@ export default {
hasAnyIssues: { hasAnyIssues: {
default: false, default: false,
}, },
hasAnyProjects: {
default: false,
},
hasBlockedIssuesFeature: { hasBlockedIssuesFeature: {
default: false, default: false,
}, },
...@@ -253,6 +258,9 @@ export default { ...@@ -253,6 +258,9 @@ export default {
showCsvButtons() { showCsvButtons() {
return this.isProject && this.isSignedIn; return this.isProject && this.isSignedIn;
}, },
showNewIssueDropdown() {
return !this.isProject && this.hasAnyProjects;
},
apiFilterParams() { apiFilterParams() {
return convertToApiParams(this.filterTokens); return convertToApiParams(this.filterTokens);
}, },
...@@ -662,6 +670,7 @@ export default { ...@@ -662,6 +670,7 @@ export default {
<gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm"> <gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
{{ $options.i18n.newIssueLabel }} {{ $options.i18n.newIssueLabel }}
</gl-button> </gl-button>
<new-issue-dropdown v-if="showNewIssueDropdown" />
</template> </template>
<template #timeframe="{ issuable = {} }"> <template #timeframe="{ issuable = {} }">
...@@ -765,6 +774,7 @@ export default { ...@@ -765,6 +774,7 @@ export default {
:export-csv-path="exportCsvPathWithQuery" :export-csv-path="exportCsvPathWithQuery"
:issuable-count="currentTabCount" :issuable-count="currentTabCount"
/> />
<new-issue-dropdown v-if="showNewIssueDropdown" />
</template> </template>
</gl-empty-state> </gl-empty-state>
<hr /> <hr />
......
<script>
import {
GlDropdown,
GlDropdownItem,
GlDropdownText,
GlLoadingIcon,
GlSearchBoxByType,
} from '@gitlab/ui';
import createFlash from '~/flash';
import searchProjectsQuery from '~/issues_list/queries/search_projects.query.graphql';
import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility';
import { __, sprintf } from '~/locale';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
export default {
i18n: {
defaultDropdownText: __('Select project to create issue'),
noMatchesFound: __('No matches found'),
toggleButtonLabel: __('Toggle project select'),
},
components: {
GlDropdown,
GlDropdownItem,
GlDropdownText,
GlLoadingIcon,
GlSearchBoxByType,
},
inject: ['fullPath'],
data() {
return {
projects: [],
search: '',
selectedProject: {},
shouldSkipQuery: true,
};
},
apollo: {
projects: {
query: searchProjectsQuery,
variables() {
return {
fullPath: this.fullPath,
search: this.search,
};
},
update: ({ group }) => group.projects.nodes ?? [],
error(error) {
createFlash({
message: __('An error occurred while loading projects.'),
captureError: true,
error,
});
},
skip() {
return this.shouldSkipQuery;
},
debounce: DEBOUNCE_DELAY,
},
},
computed: {
dropdownHref() {
return this.hasSelectedProject
? joinPaths(this.selectedProject.webUrl, DASH_SCOPE, 'issues/new')
: undefined;
},
dropdownText() {
return this.hasSelectedProject
? sprintf(__('New issue in %{project}'), { project: this.selectedProject.name })
: this.$options.i18n.defaultDropdownText;
},
hasSelectedProject() {
return this.selectedProject.id;
},
showNoSearchResultsText() {
return !this.projects.length && this.search;
},
},
methods: {
handleDropdownClick() {
if (!this.dropdownHref) {
this.$refs.dropdown.show();
}
},
handleDropdownShown() {
if (this.shouldSkipQuery) {
this.shouldSkipQuery = false;
}
this.$refs.search.focusInput();
},
selectProject(project) {
this.selectedProject = project;
},
},
};
</script>
<template>
<gl-dropdown
ref="dropdown"
right
split
:split-href="dropdownHref"
:text="dropdownText"
:toggle-text="$options.i18n.toggleButtonLabel"
variant="confirm"
@click="handleDropdownClick"
@shown="handleDropdownShown"
>
<gl-search-box-by-type ref="search" v-model.trim="search" />
<gl-loading-icon v-if="$apollo.queries.projects.loading" />
<template v-else>
<gl-dropdown-item
v-for="project of projects"
:key="project.id"
@click="selectProject(project)"
>
{{ project.nameWithNamespace }}
</gl-dropdown-item>
<gl-dropdown-text v-if="showNoSearchResultsText">
{{ $options.i18n.noMatchesFound }}
</gl-dropdown-text>
</template>
</gl-dropdown>
</template>
...@@ -121,6 +121,7 @@ export function mountIssuesListApp() { ...@@ -121,6 +121,7 @@ export function mountIssuesListApp() {
fullPath, fullPath,
groupEpicsPath, groupEpicsPath,
hasAnyIssues, hasAnyIssues,
hasAnyProjects,
hasBlockedIssuesFeature, hasBlockedIssuesFeature,
hasIssuableHealthStatusFeature, hasIssuableHealthStatusFeature,
hasIssueWeightsFeature, hasIssueWeightsFeature,
...@@ -153,6 +154,7 @@ export function mountIssuesListApp() { ...@@ -153,6 +154,7 @@ export function mountIssuesListApp() {
fullPath, fullPath,
groupEpicsPath, groupEpicsPath,
hasAnyIssues: parseBoolean(hasAnyIssues), hasAnyIssues: parseBoolean(hasAnyIssues),
hasAnyProjects: parseBoolean(hasAnyProjects),
hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature), hasBlockedIssuesFeature: parseBoolean(hasBlockedIssuesFeature),
hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature), hasIssuableHealthStatusFeature: parseBoolean(hasIssuableHealthStatusFeature),
hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
......
query searchProjects($fullPath: ID!, $search: String) {
group(fullPath: $fullPath) {
projects(search: $search, includeSubgroups: true) {
nodes {
id
name
nameWithNamespace
webUrl
}
}
}
}
export const DASH_SCOPE = '-';
const PATH_SEPARATOR = '/'; const PATH_SEPARATOR = '/';
const PATH_SEPARATOR_LEADING_REGEX = new RegExp(`^${PATH_SEPARATOR}+`); const PATH_SEPARATOR_LEADING_REGEX = new RegExp(`^${PATH_SEPARATOR}+`);
const PATH_SEPARATOR_ENDING_REGEX = new RegExp(`${PATH_SEPARATOR}+$`); const PATH_SEPARATOR_ENDING_REGEX = new RegExp(`${PATH_SEPARATOR}+$`);
......
...@@ -322,7 +322,7 @@ ...@@ -322,7 +322,7 @@
display: inline-block; display: inline-block;
} }
.btn { .btn:not(.split-content-button):not(.dropdown-toggle-split) {
margin: $gl-padding-8 $gl-padding-4; margin: $gl-padding-8 $gl-padding-4;
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
......
...@@ -238,9 +238,10 @@ module IssuesHelper ...@@ -238,9 +238,10 @@ module IssuesHelper
) )
end end
def group_issues_list_data(group, current_user, issues) def group_issues_list_data(group, current_user, issues, projects)
common_issues_list_data(group, current_user).merge( common_issues_list_data(group, current_user).merge(
has_any_issues: issues.to_a.any?.to_s has_any_issues: issues.to_a.any?.to_s,
has_any_projects: any_projects?(projects).to_s
) )
end end
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues") = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
- if Feature.enabled?(:vue_issues_list, @group, default_enabled: :yaml) - if Feature.enabled?(:vue_issues_list, @group, default_enabled: :yaml)
.js-issues-list{ data: group_issues_list_data(@group, current_user, @issues) } .js-issues-list{ data: group_issues_list_data(@group, current_user, @issues, @projects) }
- if @can_bulk_update - if @can_bulk_update
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
- else - else
......
...@@ -63,7 +63,7 @@ module EE ...@@ -63,7 +63,7 @@ module EE
end end
override :group_issues_list_data override :group_issues_list_data
def group_issues_list_data(group, current_user, issues) def group_issues_list_data(group, current_user, issues, projects)
super.tap do |data| super.tap do |data|
data[:can_bulk_update] = (can?(current_user, :admin_issue, group) && group.feature_available?(:group_bulk_edit)).to_s data[:can_bulk_update] = (can?(current_user, :admin_issue, group) && group.feature_available?(:group_bulk_edit)).to_s
......
...@@ -187,6 +187,7 @@ RSpec.describe EE::IssuesHelper do ...@@ -187,6 +187,7 @@ RSpec.describe EE::IssuesHelper do
describe '#group_issues_list_data' do describe '#group_issues_list_data' do
let(:current_user) { double.as_null_object } let(:current_user) { double.as_null_object }
let(:issues) { [] } let(:issues) { [] }
let(:projects) { [] }
before do before do
allow(helper).to receive(:current_user).and_return(current_user) allow(helper).to receive(:current_user).and_return(current_user)
...@@ -210,7 +211,7 @@ RSpec.describe EE::IssuesHelper do ...@@ -210,7 +211,7 @@ RSpec.describe EE::IssuesHelper do
group_epics_path: group_epics_path(project.group, format: :json) group_epics_path: group_epics_path(project.group, format: :json)
} }
expect(helper.group_issues_list_data(group, current_user, issues)).to include(expected) expect(helper.group_issues_list_data(group, current_user, issues, projects)).to include(expected)
end end
end end
...@@ -229,7 +230,7 @@ RSpec.describe EE::IssuesHelper do ...@@ -229,7 +230,7 @@ RSpec.describe EE::IssuesHelper do
has_multiple_issue_assignees_feature: 'false' has_multiple_issue_assignees_feature: 'false'
} }
result = helper.group_issues_list_data(group, current_user, issues) result = helper.group_issues_list_data(group, current_user, issues, projects)
expect(result).to include(expected) expect(result).to include(expected)
expect(result).not_to include(:group_epics_path) expect(result).not_to include(:group_epics_path)
......
...@@ -3703,6 +3703,9 @@ msgstr "" ...@@ -3703,6 +3703,9 @@ msgstr ""
msgid "An error occurred while loading merge requests." msgid "An error occurred while loading merge requests."
msgstr "" msgstr ""
msgid "An error occurred while loading projects."
msgstr ""
msgid "An error occurred while loading the Needs tab." msgid "An error occurred while loading the Needs tab."
msgstr "" msgstr ""
...@@ -22660,6 +22663,9 @@ msgstr "" ...@@ -22660,6 +22663,9 @@ msgstr ""
msgid "New issue" msgid "New issue"
msgstr "" msgstr ""
msgid "New issue in %{project}"
msgstr ""
msgid "New issue title" msgid "New issue title"
msgstr "" msgstr ""
...@@ -30496,6 +30502,9 @@ msgstr "" ...@@ -30496,6 +30502,9 @@ msgstr ""
msgid "Select project to choose zone" msgid "Select project to choose zone"
msgstr "" msgstr ""
msgid "Select project to create issue"
msgstr ""
msgid "Select projects" msgid "Select projects"
msgstr "" msgstr ""
......
...@@ -24,6 +24,7 @@ import IssuableByEmail from '~/issuable/components/issuable_by_email.vue'; ...@@ -24,6 +24,7 @@ import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';
import IssuableList from '~/issuable_list/components/issuable_list_root.vue'; import IssuableList from '~/issuable_list/components/issuable_list_root.vue';
import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants'; import { IssuableListTabs, IssuableStates } from '~/issuable_list/constants';
import IssuesListApp from '~/issues_list/components/issues_list_app.vue'; import IssuesListApp from '~/issues_list/components/issues_list_app.vue';
import NewIssueDropdown from '~/issues_list/components/new_issue_dropdown.vue';
import { import {
CREATED_DESC, CREATED_DESC,
DUE_DATE_OVERDUE, DUE_DATE_OVERDUE,
...@@ -65,6 +66,7 @@ describe('IssuesListApp component', () => { ...@@ -65,6 +66,7 @@ describe('IssuesListApp component', () => {
exportCsvPath: 'export/csv/path', exportCsvPath: 'export/csv/path',
fullPath: 'path/to/project', fullPath: 'path/to/project',
hasAnyIssues: true, hasAnyIssues: true,
hasAnyProjects: true,
hasBlockedIssuesFeature: true, hasBlockedIssuesFeature: true,
hasIssueWeightsFeature: true, hasIssueWeightsFeature: true,
hasIterationsFeature: true, hasIterationsFeature: true,
...@@ -93,6 +95,7 @@ describe('IssuesListApp component', () => { ...@@ -93,6 +95,7 @@ describe('IssuesListApp component', () => {
const findGlEmptyState = () => wrapper.findComponent(GlEmptyState); const findGlEmptyState = () => wrapper.findComponent(GlEmptyState);
const findGlLink = () => wrapper.findComponent(GlLink); const findGlLink = () => wrapper.findComponent(GlLink);
const findIssuableList = () => wrapper.findComponent(IssuableList); const findIssuableList = () => wrapper.findComponent(IssuableList);
const findNewIssueDropdown = () => wrapper.findComponent(NewIssueDropdown);
const mountComponent = ({ const mountComponent = ({
provide = {}, provide = {},
...@@ -190,10 +193,7 @@ describe('IssuesListApp component', () => { ...@@ -190,10 +193,7 @@ describe('IssuesListApp component', () => {
beforeEach(() => { beforeEach(() => {
setWindowLocation(search); setWindowLocation(search);
wrapper = mountComponent({ wrapper = mountComponent({ provide: { isSignedIn: true }, mountFn: mount });
provide: { isSignedIn: true },
mountFn: mount,
});
jest.runOnlyPendingTimers(); jest.runOnlyPendingTimers();
}); });
...@@ -208,7 +208,7 @@ describe('IssuesListApp component', () => { ...@@ -208,7 +208,7 @@ describe('IssuesListApp component', () => {
describe('when user is not signed in', () => { describe('when user is not signed in', () => {
it('does not render', () => { it('does not render', () => {
wrapper = mountComponent({ provide: { isSignedIn: false } }); wrapper = mountComponent({ provide: { isSignedIn: false }, mountFn: mount });
expect(findCsvImportExportButtons().exists()).toBe(false); expect(findCsvImportExportButtons().exists()).toBe(false);
}); });
...@@ -216,7 +216,7 @@ describe('IssuesListApp component', () => { ...@@ -216,7 +216,7 @@ describe('IssuesListApp component', () => {
describe('when in a group context', () => { describe('when in a group context', () => {
it('does not render', () => { it('does not render', () => {
wrapper = mountComponent({ provide: { isProject: false } }); wrapper = mountComponent({ provide: { isProject: false }, mountFn: mount });
expect(findCsvImportExportButtons().exists()).toBe(false); expect(findCsvImportExportButtons().exists()).toBe(false);
}); });
...@@ -231,7 +231,7 @@ describe('IssuesListApp component', () => { ...@@ -231,7 +231,7 @@ describe('IssuesListApp component', () => {
}); });
it('does not render when user does not have permissions', () => { it('does not render when user does not have permissions', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: false } }); wrapper = mountComponent({ provide: { canBulkUpdate: false }, mountFn: mount });
expect(findGlButtons().filter((button) => button.text() === 'Edit issues')).toHaveLength(0); expect(findGlButtons().filter((button) => button.text() === 'Edit issues')).toHaveLength(0);
}); });
...@@ -258,11 +258,25 @@ describe('IssuesListApp component', () => { ...@@ -258,11 +258,25 @@ describe('IssuesListApp component', () => {
}); });
it('does not render when user does not have permissions', () => { it('does not render when user does not have permissions', () => {
wrapper = mountComponent({ provide: { showNewIssueLink: false } }); wrapper = mountComponent({ provide: { showNewIssueLink: false }, mountFn: mount });
expect(findGlButtons().filter((button) => button.text() === 'New issue')).toHaveLength(0); expect(findGlButtons().filter((button) => button.text() === 'New issue')).toHaveLength(0);
}); });
}); });
describe('new issue split dropdown', () => {
it('does not render in a project context', () => {
wrapper = mountComponent({ provide: { isProject: true }, mountFn: mount });
expect(findNewIssueDropdown().exists()).toBe(false);
});
it('renders in a group context', () => {
wrapper = mountComponent({ provide: { isProject: false }, mountFn: mount });
expect(findNewIssueDropdown().exists()).toBe(true);
});
});
}); });
describe('initial url params', () => { describe('initial url params', () => {
......
import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import NewIssueDropdown from '~/issues_list/components/new_issue_dropdown.vue';
import searchProjectsQuery from '~/issues_list/queries/search_projects.query.graphql';
import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility';
import {
emptySearchProjectsQueryResponse,
project1,
project2,
searchProjectsQueryResponse,
} from '../mock_data';
describe('NewIssueDropdown component', () => {
let wrapper;
const localVue = createLocalVue();
localVue.use(VueApollo);
const mountComponent = ({
search = '',
queryResponse = searchProjectsQueryResponse,
mountFn = shallowMount,
} = {}) => {
const requestHandlers = [[searchProjectsQuery, jest.fn().mockResolvedValue(queryResponse)]];
const apolloProvider = createMockApollo(requestHandlers);
return mountFn(NewIssueDropdown, {
localVue,
apolloProvider,
provide: {
fullPath: 'mushroom-kingdom',
},
data() {
return { search };
},
});
};
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
const showDropdown = async () => {
findDropdown().vm.$emit('shown');
await wrapper.vm.$apollo.queries.projects.refetch();
jest.runOnlyPendingTimers();
};
afterEach(() => {
wrapper.destroy();
});
it('renders a split dropdown', () => {
wrapper = mountComponent();
expect(findDropdown().props('split')).toBe(true);
});
it('renders a label for the dropdown toggle button', () => {
wrapper = mountComponent();
expect(findDropdown().attributes('toggle-text')).toBe(NewIssueDropdown.i18n.toggleButtonLabel);
});
it('focuses on input when dropdown is shown', async () => {
wrapper = mountComponent({ mountFn: mount });
const inputSpy = jest.spyOn(findInput().vm, 'focusInput');
await showDropdown();
expect(inputSpy).toHaveBeenCalledTimes(1);
});
it('renders expected dropdown items', async () => {
wrapper = mountComponent({ mountFn: mount });
await showDropdown();
const listItems = wrapper.findAll('li');
expect(listItems.at(0).text()).toBe(project1.nameWithNamespace);
expect(listItems.at(1).text()).toBe(project2.nameWithNamespace);
});
it('renders `No matches found` when there are no matches', async () => {
wrapper = mountComponent({
search: 'no matches',
queryResponse: emptySearchProjectsQueryResponse,
mountFn: mount,
});
await showDropdown();
expect(wrapper.find('li').text()).toBe(NewIssueDropdown.i18n.noMatchesFound);
});
describe('when no project is selected', () => {
beforeEach(() => {
wrapper = mountComponent();
});
it('dropdown button is not a link', () => {
expect(findDropdown().attributes('split-href')).toBeUndefined();
});
it('displays default text on the dropdown button', () => {
expect(findDropdown().props('text')).toBe(NewIssueDropdown.i18n.defaultDropdownText);
});
});
describe('when a project is selected', () => {
beforeEach(async () => {
wrapper = mountComponent({ mountFn: mount });
await showDropdown();
wrapper.findComponent(GlDropdownItem).vm.$emit('click', project1);
});
it('dropdown button is a link', () => {
const href = joinPaths(project1.webUrl, DASH_SCOPE, 'issues/new');
expect(findDropdown().attributes('split-href')).toBe(href);
});
it('displays project name on the dropdown button', () => {
expect(findDropdown().props('text')).toBe(`New issue in ${project1.name}`);
});
});
});
...@@ -221,3 +221,37 @@ export const urlParamsWithSpecialValues = { ...@@ -221,3 +221,37 @@ export const urlParamsWithSpecialValues = {
epic_id: 'None', epic_id: 'None',
weight: 'None', weight: 'None',
}; };
export const project1 = {
id: 'gid://gitlab/Group/26',
name: 'Super Mario Project',
nameWithNamespace: 'Mushroom Kingdom / Super Mario Project',
webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/super-mario-project',
};
export const project2 = {
id: 'gid://gitlab/Group/59',
name: 'Mario Kart Project',
nameWithNamespace: 'Mushroom Kingdom / Mario Kart Project',
webUrl: 'https://127.0.0.1:3000/mushroom-kingdom/mario-kart-project',
};
export const searchProjectsQueryResponse = {
data: {
group: {
projects: {
nodes: [project1, project2],
},
},
},
};
export const emptySearchProjectsQueryResponse = {
data: {
group: {
projects: {
nodes: [],
},
},
},
};
...@@ -354,6 +354,7 @@ RSpec.describe IssuesHelper do ...@@ -354,6 +354,7 @@ RSpec.describe IssuesHelper do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:current_user) { double.as_null_object } let(:current_user) { double.as_null_object }
let(:issues) { [] } let(:issues) { [] }
let(:projects) { [] }
it 'returns expected result' do it 'returns expected result' do
allow(helper).to receive(:current_user).and_return(current_user) allow(helper).to receive(:current_user).and_return(current_user)
...@@ -367,13 +368,14 @@ RSpec.describe IssuesHelper do ...@@ -367,13 +368,14 @@ RSpec.describe IssuesHelper do
empty_state_svg_path: '#', empty_state_svg_path: '#',
full_path: group.full_path, full_path: group.full_path,
has_any_issues: issues.to_a.any?.to_s, has_any_issues: issues.to_a.any?.to_s,
has_any_projects: any_projects?(projects).to_s,
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'), jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'),
rss_path: '#', rss_path: '#',
sign_in_path: new_user_session_path sign_in_path: new_user_session_path
} }
expect(helper.group_issues_list_data(group, current_user, issues)).to include(expected) expect(helper.group_issues_list_data(group, current_user, issues, projects)).to include(expected)
end 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