Commit 423c5f60 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '322755-add-email-issue-to-project' into 'master'

Add email issue to project modal to issues page refactor

See merge request gitlab-org/gitlab!60430
parents feda19e6 8a2b26e6
...@@ -12,6 +12,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus'; ...@@ -12,6 +12,7 @@ import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { toNumber } from 'lodash'; import { toNumber } from 'lodash';
import createFlash from '~/flash'; import createFlash from '~/flash';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
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 { import {
...@@ -54,6 +55,7 @@ export default { ...@@ -54,6 +55,7 @@ export default {
GlIcon, GlIcon,
GlLink, GlLink,
GlSprintf, GlSprintf,
IssuableByEmail,
IssuableList, IssuableList,
IssueCardTimeInfo, IssueCardTimeInfo,
BlockingIssuesCount: () => import('ee_component/issues/components/blocking_issues_count.vue'), BlockingIssuesCount: () => import('ee_component/issues/components/blocking_issues_count.vue'),
...@@ -86,6 +88,9 @@ export default { ...@@ -86,6 +88,9 @@ export default {
hasIssues: { hasIssues: {
default: false, default: false,
}, },
initialEmail: {
default: '',
},
isSignedIn: { isSignedIn: {
default: false, default: false,
}, },
...@@ -376,8 +381,8 @@ export default { ...@@ -376,8 +381,8 @@ export default {
</script> </script>
<template> <template>
<div v-if="hasIssues">
<issuable-list <issuable-list
v-if="hasIssues"
:namespace="projectPath" :namespace="projectPath"
recent-searches-storage-key="issues" recent-searches-storage-key="issues"
:search-input-placeholder="__('Search or filter results…')" :search-input-placeholder="__('Search or filter results…')"
...@@ -514,6 +519,9 @@ export default { ...@@ -514,6 +519,9 @@ export default {
</template> </template>
</issuable-list> </issuable-list>
<issuable-by-email v-if="initialEmail" class="gl-text-center gl-pt-5 gl-pb-7" />
</div>
<div v-else-if="isSignedIn"> <div v-else-if="isSignedIn">
<gl-empty-state <gl-empty-state
:description="$options.i18n.noIssuesSignedInDescription" :description="$options.i18n.noIssuesSignedInDescription"
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { IssuableType } from '~/issue_show/constants';
import IssuesListApp from '~/issues_list/components/issues_list_app.vue'; import IssuesListApp from '~/issues_list/components/issues_list_app.vue';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
...@@ -80,6 +81,7 @@ export function initIssuesListApp() { ...@@ -80,6 +81,7 @@ export function initIssuesListApp() {
canEdit, canEdit,
canImportIssues, canImportIssues,
email, email,
emailsHelpPagePath,
emptyStateSvgPath, emptyStateSvgPath,
endpoint, endpoint,
exportCsvPath, exportCsvPath,
...@@ -88,15 +90,19 @@ export function initIssuesListApp() { ...@@ -88,15 +90,19 @@ export function initIssuesListApp() {
hasIssues, hasIssues,
hasIssueWeightsFeature, hasIssueWeightsFeature,
importCsvIssuesPath, importCsvIssuesPath,
initialEmail,
isSignedIn, isSignedIn,
issuesPath, issuesPath,
jiraIntegrationPath, jiraIntegrationPath,
markdownHelpPath,
maxAttachmentSize, maxAttachmentSize,
newIssuePath, newIssuePath,
projectImportJiraPath, projectImportJiraPath,
projectLabelsPath, projectLabelsPath,
projectMilestonesPath, projectMilestonesPath,
projectPath, projectPath,
quickActionsHelpPath,
resetPath,
rssPath, rssPath,
showNewIssueLink, showNewIssueLink,
signInPath, signInPath,
...@@ -138,6 +144,13 @@ export function initIssuesListApp() { ...@@ -138,6 +144,13 @@ export function initIssuesListApp() {
showExportButton: parseBoolean(hasIssues), showExportButton: parseBoolean(hasIssues),
showImportButton: parseBoolean(canImportIssues), showImportButton: parseBoolean(canImportIssues),
showLabel: !parseBoolean(hasIssues), showLabel: !parseBoolean(hasIssues),
// For IssuableByEmail component
emailsHelpPagePath,
initialEmail,
issuableType: IssuableType.Issue,
markdownHelpPath,
quickActionsHelpPath,
resetPath,
}, },
render: (createComponent) => createComponent(IssuesListApp), render: (createComponent) => createComponent(IssuesListApp),
}); });
......
...@@ -1032,11 +1032,6 @@ pre.light-well { ...@@ -1032,11 +1032,6 @@ pre.light-well {
} }
} }
.issuable-footer {
padding-top: $gl-padding;
padding-bottom: 37px;
}
.project-ci-linter { .project-ci-linter {
.ci-editor { .ci-editor {
height: 400px; height: 400px;
......
...@@ -172,20 +172,25 @@ module IssuesHelper ...@@ -172,20 +172,25 @@ module IssuesHelper
can_edit: can?(current_user, :admin_project, project).to_s, can_edit: can?(current_user, :admin_project, project).to_s,
can_import_issues: can?(current_user, :import_issues, @project).to_s, can_import_issues: can?(current_user, :import_issues, @project).to_s,
email: current_user&.notification_email, email: current_user&.notification_email,
emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'),
empty_state_svg_path: image_path('illustrations/issues.svg'), empty_state_svg_path: image_path('illustrations/issues.svg'),
endpoint: expose_path(api_v4_projects_issues_path(id: project.id)), endpoint: expose_path(api_v4_projects_issues_path(id: project.id)),
export_csv_path: export_csv_project_issues_path(project), export_csv_path: export_csv_project_issues_path(project),
has_issues: project_issues(project).exists?.to_s, has_issues: project_issues(project).exists?.to_s,
import_csv_issues_path: import_csv_namespace_project_issues_path, import_csv_issues_path: import_csv_namespace_project_issues_path,
initial_email: project.new_issuable_address(current_user, 'issue'),
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
issues_path: project_issues_path(project), issues_path: project_issues_path(project),
jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'), jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'),
markdown_help_path: help_page_path('user/markdown'),
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }), new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.try(:id), milestone_id: finder.milestones.first.try(:id) }),
project_import_jira_path: project_import_jira_path(project), project_import_jira_path: project_import_jira_path(project),
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json), project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
project_milestones_path: project_milestones_path(project, format: :json), project_milestones_path: project_milestones_path(project, format: :json),
project_path: project.full_path, project_path: project.full_path,
quick_actions_help_path: help_page_path('user/project/quick_actions'),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
rss_path: url_for(safe_params.merge(rss_url_options)), rss_path: url_for(safe_params.merge(rss_url_options)),
show_new_issue_link: show_new_issue_link?(project).to_s, show_new_issue_link: show_new_issue_link?(project).to_s,
sign_in_path: new_user_session_path sign_in_path: new_user_session_path
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
.issues-holder .issues-holder
= render 'issues' = render 'issues'
- if new_issue_email - if new_issue_email
.issuable-footer.text-center .gl-text-center.gl-pt-5.gl-pb-7
.js-issueable-by-email{ data: { initial_email: new_issue_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } } .js-issueable-by-email{ data: { initial_email: new_issue_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } }
- else - else
- new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project) - new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project)
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
.merge-requests-holder .merge-requests-holder
= render 'merge_requests' = render 'merge_requests'
- if new_merge_request_email - if new_merge_request_email
.issuable-footer.text-center .gl-text-center.gl-pt-5.gl-pb-7
.js-issueable-by-email{ data: { initial_email: new_merge_request_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } } .js-issueable-by-email{ data: { initial_email: new_merge_request_email, issuable_type: issuable_type, emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'), quick_actions_help_path: help_page_path('user/project/quick_actions'), markdown_help_path: help_page_path('user/markdown'), reset_path: new_issuable_address_project_path(@project, issuable_type: issuable_type) } }
- else - else
= render 'shared/empty_states/merge_requests', button_path: new_merge_request_path = render 'shared/empty_states/merge_requests', button_path: new_merge_request_path
...@@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { apiParams, filteredTokens, locationSearch, urlParams } from 'jest/issues_list/mock_data'; import { apiParams, filteredTokens, locationSearch, urlParams } from 'jest/issues_list/mock_data';
import createFlash from '~/flash'; import createFlash from '~/flash';
import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue'; import CsvImportExportButtons from '~/issuable/components/csv_import_export_buttons.vue';
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';
...@@ -24,7 +25,6 @@ import { setUrlParams } from '~/lib/utils/url_utility'; ...@@ -24,7 +25,6 @@ import { setUrlParams } from '~/lib/utils/url_utility';
jest.mock('~/flash'); jest.mock('~/flash');
describe('IssuesListApp component', () => { describe('IssuesListApp component', () => {
const originalWindowLocation = window.location;
let axiosMock; let axiosMock;
let wrapper; let wrapper;
...@@ -65,6 +65,7 @@ describe('IssuesListApp component', () => { ...@@ -65,6 +65,7 @@ describe('IssuesListApp component', () => {
}; };
const findCsvImportExportButtons = () => wrapper.findComponent(CsvImportExportButtons); const findCsvImportExportButtons = () => wrapper.findComponent(CsvImportExportButtons);
const findIssuableByEmail = () => wrapper.findComponent(IssuableByEmail);
const findGlButton = () => wrapper.findComponent(GlButton); const findGlButton = () => wrapper.findComponent(GlButton);
const findGlButtons = () => wrapper.findAllComponents(GlButton); const findGlButtons = () => wrapper.findAllComponents(GlButton);
const findGlButtonAt = (index) => findGlButtons().at(index); const findGlButtonAt = (index) => findGlButtons().at(index);
...@@ -88,7 +89,7 @@ describe('IssuesListApp component', () => { ...@@ -88,7 +89,7 @@ describe('IssuesListApp component', () => {
}); });
afterEach(() => { afterEach(() => {
window.location = originalWindowLocation; global.jsdom.reconfigure({ url: TEST_HOST });
axiosMock.reset(); axiosMock.reset();
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -122,34 +123,31 @@ describe('IssuesListApp component', () => { ...@@ -122,34 +123,31 @@ describe('IssuesListApp component', () => {
describe('header action buttons', () => { describe('header action buttons', () => {
it('renders rss button', () => { it('renders rss button', () => {
wrapper = mountComponent(); wrapper = mountComponent({ mountFn: mount });
expect(findGlButtonAt(0).props('icon')).toBe('rss');
expect(findGlButtonAt(0).attributes()).toMatchObject({ expect(findGlButtonAt(0).attributes()).toMatchObject({
href: defaultProvide.rssPath, href: defaultProvide.rssPath,
icon: 'rss',
'aria-label': IssuesListApp.i18n.rssLabel, 'aria-label': IssuesListApp.i18n.rssLabel,
}); });
}); });
it('renders calendar button', () => { it('renders calendar button', () => {
wrapper = mountComponent(); wrapper = mountComponent({ mountFn: mount });
expect(findGlButtonAt(1).props('icon')).toBe('calendar');
expect(findGlButtonAt(1).attributes()).toMatchObject({ expect(findGlButtonAt(1).attributes()).toMatchObject({
href: defaultProvide.calendarPath, href: defaultProvide.calendarPath,
icon: 'calendar',
'aria-label': IssuesListApp.i18n.calendarLabel, 'aria-label': IssuesListApp.i18n.calendarLabel,
}); });
}); });
it('renders csv import/export component', async () => { it('renders csv import/export component', async () => {
const search = '?page=1&search=refactor'; const search = '?page=1&search=refactor&state=opened&order_by=created_at&sort=desc';
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: `${TEST_HOST}${search}` });
writable: true,
value: { search },
});
wrapper = mountComponent(); wrapper = mountComponent({ mountFn: mount });
await waitForPromises(); await waitForPromises();
...@@ -161,7 +159,7 @@ describe('IssuesListApp component', () => { ...@@ -161,7 +159,7 @@ describe('IssuesListApp component', () => {
describe('bulk edit button', () => { describe('bulk edit button', () => {
it('renders when user has permissions', () => { it('renders when user has permissions', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: true } }); wrapper = mountComponent({ provide: { canBulkUpdate: true }, mountFn: mount });
expect(findGlButtonAt(2).text()).toBe('Edit issues'); expect(findGlButtonAt(2).text()).toBe('Edit issues');
}); });
...@@ -173,7 +171,7 @@ describe('IssuesListApp component', () => { ...@@ -173,7 +171,7 @@ describe('IssuesListApp component', () => {
}); });
it('emits "issuables:enableBulkEdit" event to legacy bulk edit class', () => { it('emits "issuables:enableBulkEdit" event to legacy bulk edit class', () => {
wrapper = mountComponent({ provide: { canBulkUpdate: true } }); wrapper = mountComponent({ provide: { canBulkUpdate: true }, mountFn: mount });
jest.spyOn(eventHub, '$emit'); jest.spyOn(eventHub, '$emit');
...@@ -185,7 +183,7 @@ describe('IssuesListApp component', () => { ...@@ -185,7 +183,7 @@ describe('IssuesListApp component', () => {
describe('new issue button', () => { describe('new issue button', () => {
it('renders when user has permissions', () => { it('renders when user has permissions', () => {
wrapper = mountComponent({ provide: { showNewIssueLink: true } }); wrapper = mountComponent({ provide: { showNewIssueLink: true }, mountFn: mount });
expect(findGlButtonAt(2).text()).toBe('New issue'); expect(findGlButtonAt(2).text()).toBe('New issue');
expect(findGlButtonAt(2).attributes('href')).toBe(defaultProvide.newIssuePath); expect(findGlButtonAt(2).attributes('href')).toBe(defaultProvide.newIssuePath);
...@@ -204,10 +202,7 @@ describe('IssuesListApp component', () => { ...@@ -204,10 +202,7 @@ describe('IssuesListApp component', () => {
it('is set from the url params', () => { it('is set from the url params', () => {
const page = 5; const page = 5;
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: setUrlParams({ page }, TEST_HOST) });
writable: true,
value: { href: setUrlParams({ page }, TEST_HOST) },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -217,10 +212,7 @@ describe('IssuesListApp component', () => { ...@@ -217,10 +212,7 @@ describe('IssuesListApp component', () => {
describe('search', () => { describe('search', () => {
it('is set from the url params', () => { it('is set from the url params', () => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: `${TEST_HOST}${locationSearch}` });
writable: true,
value: { search: locationSearch },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -230,10 +222,7 @@ describe('IssuesListApp component', () => { ...@@ -230,10 +222,7 @@ describe('IssuesListApp component', () => {
describe('sort', () => { describe('sort', () => {
it.each(Object.keys(sortParams))('is set as %s from the url params', (sortKey) => { it.each(Object.keys(sortParams))('is set as %s from the url params', (sortKey) => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: setUrlParams(sortParams[sortKey], TEST_HOST) });
writable: true,
value: { href: setUrlParams(sortParams[sortKey], TEST_HOST) },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -248,10 +237,7 @@ describe('IssuesListApp component', () => { ...@@ -248,10 +237,7 @@ describe('IssuesListApp component', () => {
it('is set from the url params', () => { it('is set from the url params', () => {
const initialState = IssuableStates.All; const initialState = IssuableStates.All;
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: setUrlParams({ state: initialState }, TEST_HOST) });
writable: true,
value: { href: setUrlParams({ state: initialState }, TEST_HOST) },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -261,10 +247,7 @@ describe('IssuesListApp component', () => { ...@@ -261,10 +247,7 @@ describe('IssuesListApp component', () => {
describe('filter tokens', () => { describe('filter tokens', () => {
it('is set from the url params', () => { it('is set from the url params', () => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: `${TEST_HOST}${locationSearch}` });
writable: true,
value: { search: locationSearch },
});
wrapper = mountComponent(); wrapper = mountComponent();
...@@ -290,16 +273,25 @@ describe('IssuesListApp component', () => { ...@@ -290,16 +273,25 @@ describe('IssuesListApp component', () => {
); );
}); });
describe('IssuableByEmail component', () => {
describe.each([true, false])(`when issue creation by email is enabled=%s`, (enabled) => {
it(`${enabled ? 'renders' : 'does not render'}`, () => {
wrapper = mountComponent({ provide: { initialEmail: enabled } });
expect(findIssuableByEmail().exists()).toBe(enabled);
});
});
});
describe('empty states', () => { describe('empty states', () => {
describe('when there are issues', () => { describe('when there are issues', () => {
describe('when search returns no results', () => { describe('when search returns no results', () => {
beforeEach(() => { beforeEach(async () => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({ url: `${TEST_HOST}?search=no+results` });
writable: true,
value: { search: '?search=no+results' }, wrapper = mountComponent({ provide: { hasIssues: true }, mountFn: mount });
});
wrapper = mountComponent({ provide: { hasIssues: true } }); await waitForPromises();
}); });
it('shows empty state', () => { it('shows empty state', () => {
...@@ -312,8 +304,10 @@ describe('IssuesListApp component', () => { ...@@ -312,8 +304,10 @@ describe('IssuesListApp component', () => {
}); });
describe('when "Open" tab has no issues', () => { describe('when "Open" tab has no issues', () => {
beforeEach(() => { beforeEach(async () => {
wrapper = mountComponent({ provide: { hasIssues: true } }); wrapper = mountComponent({ provide: { hasIssues: true }, mountFn: mount });
await waitForPromises();
}); });
it('shows empty state', () => { it('shows empty state', () => {
...@@ -327,12 +321,13 @@ describe('IssuesListApp component', () => { ...@@ -327,12 +321,13 @@ describe('IssuesListApp component', () => {
describe('when "Closed" tab has no issues', () => { describe('when "Closed" tab has no issues', () => {
beforeEach(async () => { beforeEach(async () => {
Object.defineProperty(window, 'location', { global.jsdom.reconfigure({
writable: true, url: setUrlParams({ state: IssuableStates.Closed }, TEST_HOST),
value: { href: setUrlParams({ state: IssuableStates.Closed }, TEST_HOST) },
}); });
wrapper = mountComponent({ provide: { hasIssues: true } }); wrapper = mountComponent({ provide: { hasIssues: true }, mountFn: mount });
await waitForPromises();
}); });
it('shows empty state', () => { it('shows empty state', () => {
......
...@@ -300,20 +300,25 @@ RSpec.describe IssuesHelper do ...@@ -300,20 +300,25 @@ RSpec.describe IssuesHelper do
can_edit: 'true', can_edit: 'true',
can_import_issues: 'true', can_import_issues: 'true',
email: current_user&.notification_email, email: current_user&.notification_email,
emails_help_page_path: help_page_path('development/emails', anchor: 'email-namespace'),
empty_state_svg_path: '#', empty_state_svg_path: '#',
endpoint: expose_path(api_v4_projects_issues_path(id: project.id)), endpoint: expose_path(api_v4_projects_issues_path(id: project.id)),
export_csv_path: export_csv_project_issues_path(project), export_csv_path: export_csv_project_issues_path(project),
has_issues: project_issues(project).exists?.to_s, has_issues: project_issues(project).exists?.to_s,
import_csv_issues_path: '#', import_csv_issues_path: '#',
initial_email: project.new_issuable_address(current_user, 'issue'),
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
issues_path: project_issues_path(project), issues_path: project_issues_path(project),
jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'), jira_integration_path: help_page_url('user/project/integrations/jira', anchor: 'view-jira-issues'),
markdown_help_path: help_page_path('user/markdown'),
max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes), max_attachment_size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes),
new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.id, milestone_id: finder.milestones.first.id }), new_issue_path: new_project_issue_path(project, issue: { assignee_id: finder.assignee.id, milestone_id: finder.milestones.first.id }),
project_import_jira_path: project_import_jira_path(project), project_import_jira_path: project_import_jira_path(project),
project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json), project_labels_path: project_labels_path(project, include_ancestor_groups: true, format: :json),
project_milestones_path: project_milestones_path(project, format: :json), project_milestones_path: project_milestones_path(project, format: :json),
project_path: project.full_path, project_path: project.full_path,
quick_actions_help_path: help_page_path('user/project/quick_actions'),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
rss_path: '#', rss_path: '#',
show_new_issue_link: 'true', show_new_issue_link: 'true',
sign_in_path: new_user_session_path sign_in_path: new_user_session_path
......
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