Commit 490fd60f authored by Peter Leitzen's avatar Peter Leitzen

Merge branch '219381-manual-incident-creation' into 'master'

Create incident from the list page

See merge request gitlab-org/gitlab!37802
parents fc4f0d40 a1123ec1
...@@ -7,9 +7,11 @@ import { ...@@ -7,9 +7,11 @@ import {
GlAvatarLink, GlAvatarLink,
GlAvatar, GlAvatar,
GlTooltipDirective, GlTooltipDirective,
GlButton,
} from '@gitlab/ui'; } from '@gitlab/ui';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import getIncidents from '../graphql/queries/get_incidents.query.graphql'; import getIncidents from '../graphql/queries/get_incidents.query.graphql';
import { I18N } from '../constants'; import { I18N } from '../constants';
...@@ -48,12 +50,13 @@ export default { ...@@ -48,12 +50,13 @@ export default {
GlAvatarsInline, GlAvatarsInline,
GlAvatarLink, GlAvatarLink,
GlAvatar, GlAvatar,
GlButton,
TimeAgoTooltip, TimeAgoTooltip,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
inject: ['projectPath'], inject: ['projectPath', 'newIssuePath', 'incidentTemplateName'],
apollo: { apollo: {
incidents: { incidents: {
query: getIncidents, query: getIncidents,
...@@ -73,6 +76,7 @@ export default { ...@@ -73,6 +76,7 @@ export default {
return { return {
errored: false, errored: false,
isErrorAlertDismissed: false, isErrorAlertDismissed: false,
redirecting: false,
}; };
}, },
computed: { computed: {
...@@ -90,6 +94,9 @@ export default { ...@@ -90,6 +94,9 @@ export default {
[bodyTrClass]: !this.loading && this.hasIncidents, [bodyTrClass]: !this.loading && this.hasIncidents,
}; };
}, },
newIncidentPath() {
return mergeUrlParams({ issuable_template: this.incidentTemplateName }, this.newIssuePath);
},
}, },
methods: { methods: {
hasAssignees(assignees) { hasAssignees(assignees) {
...@@ -104,6 +111,21 @@ export default { ...@@ -104,6 +111,21 @@ export default {
{{ $options.i18n.errorMsg }} {{ $options.i18n.errorMsg }}
</gl-alert> </gl-alert>
<div class="gl-display-flex gl-justify-content-end">
<gl-button
class="gl-mt-3 create-incident-button"
data-testid="createIncidentBtn"
:loading="redirecting"
:disabled="redirecting"
category="primary"
variant="success"
:href="newIncidentPath"
@click="redirecting = true"
>
{{ $options.i18n.createIncidentBtnLabel }}
</gl-button>
</div>
<h4 class="gl-display-block d-md-none my-3"> <h4 class="gl-display-block d-md-none my-3">
{{ s__('IncidentManagement|Incidents') }} {{ s__('IncidentManagement|Incidents') }}
</h4> </h4>
......
...@@ -5,4 +5,5 @@ export const I18N = { ...@@ -5,4 +5,5 @@ export const I18N = {
errorMsg: s__('IncidentManagement|There was an error displaying the incidents.'), errorMsg: s__('IncidentManagement|There was an error displaying the incidents.'),
noIncidents: s__('IncidentManagement|No incidents to display.'), noIncidents: s__('IncidentManagement|No incidents to display.'),
unassigned: s__('IncidentManagement|Unassigned'), unassigned: s__('IncidentManagement|Unassigned'),
createIncidentBtnLabel: s__('IncidentManagement|Create incident'),
}; };
...@@ -8,7 +8,7 @@ export default () => { ...@@ -8,7 +8,7 @@ export default () => {
const selector = '#js-incidents'; const selector = '#js-incidents';
const domEl = document.querySelector(selector); const domEl = document.querySelector(selector);
const { projectPath } = domEl.dataset; const { projectPath, newIssuePath, incidentTemplateName } = domEl.dataset;
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(), defaultClient: createDefaultClient(),
...@@ -18,17 +18,15 @@ export default () => { ...@@ -18,17 +18,15 @@ export default () => {
el: selector, el: selector,
provide: { provide: {
projectPath, projectPath,
incidentTemplateName,
newIssuePath,
}, },
apolloProvider, apolloProvider,
components: { components: {
IncidentsList, IncidentsList,
}, },
render(createElement) { render(createElement) {
return createElement('incidents-list', { return createElement('incidents-list');
props: {
projectPath,
},
});
}, },
}); });
}; };
...@@ -88,4 +88,10 @@ ...@@ -88,4 +88,10 @@
background-color: $white-normal; background-color: $white-normal;
} }
} }
@include media-breakpoint-down(xs) {
.create-incident-button {
@include gl-w-full;
}
}
} }
...@@ -3,7 +3,9 @@ ...@@ -3,7 +3,9 @@
module Projects::IncidentsHelper module Projects::IncidentsHelper
def incidents_data(project) def incidents_data(project)
{ {
'project-path' => project.full_path 'project-path' => project.full_path,
'new-issue-path' => new_project_issue_path(project),
'incident-template-name' => 'incident'
} }
end end
end end
---
title: Create incident from the incidents list page
merge_request: 37802
author:
type: added
...@@ -12701,6 +12701,9 @@ msgstr "" ...@@ -12701,6 +12701,9 @@ msgstr ""
msgid "IncidentManagement|Assignees" msgid "IncidentManagement|Assignees"
msgstr "" msgstr ""
msgid "IncidentManagement|Create incident"
msgstr ""
msgid "IncidentManagement|Date created" msgid "IncidentManagement|Date created"
msgstr "" msgstr ""
......
...@@ -7,6 +7,8 @@ import mockIncidents from '../mocks/incidents.json'; ...@@ -7,6 +7,8 @@ import mockIncidents from '../mocks/incidents.json';
describe('Incidents List', () => { describe('Incidents List', () => {
let wrapper; let wrapper;
const newIssuePath = 'namespace/project/-/issues/new';
const incidentTemplateName = 'incident';
const findTable = () => wrapper.find(GlTable); const findTable = () => wrapper.find(GlTable);
const findTableRows = () => wrapper.findAll('table tbody tr'); const findTableRows = () => wrapper.findAll('table tbody tr');
...@@ -14,6 +16,7 @@ describe('Incidents List', () => { ...@@ -14,6 +16,7 @@ describe('Incidents List', () => {
const findLoader = () => wrapper.find(GlLoadingIcon); const findLoader = () => wrapper.find(GlLoadingIcon);
const findTimeAgo = () => wrapper.findAll(TimeAgoTooltip); const findTimeAgo = () => wrapper.findAll(TimeAgoTooltip);
const findAssingees = () => wrapper.findAll('[data-testid="incident-assignees"]'); const findAssingees = () => wrapper.findAll('[data-testid="incident-assignees"]');
const findCreateIncidentBtn = () => wrapper.find('[data-testid="createIncidentBtn"]');
function mountComponent({ data = { incidents: [] }, loading = false }) { function mountComponent({ data = { incidents: [] }, loading = false }) {
wrapper = mount(IncidentsList, { wrapper = mount(IncidentsList, {
...@@ -31,8 +34,11 @@ describe('Incidents List', () => { ...@@ -31,8 +34,11 @@ describe('Incidents List', () => {
}, },
provide: { provide: {
projectPath: '/project/path', projectPath: '/project/path',
newIssuePath,
incidentTemplateName,
}, },
stubs: { stubs: {
GlButton: true,
GlAvatar: true, GlAvatar: true,
}, },
}); });
...@@ -107,4 +113,27 @@ describe('Incidents List', () => { ...@@ -107,4 +113,27 @@ describe('Incidents List', () => {
}); });
}); });
}); });
describe('Create Incident', () => {
beforeEach(() => {
mountComponent({
data: { incidents: [] },
loading: false,
});
});
it('shows the button linking to new incidents page with prefilled incident template', () => {
expect(findCreateIncidentBtn().exists()).toBe(true);
expect(findCreateIncidentBtn().attributes('href')).toBe(
`${newIssuePath}?issuable_template=${incidentTemplateName}`,
);
});
it('sets button loading on click', () => {
findCreateIncidentBtn().vm.$emit('click');
return wrapper.vm.$nextTick().then(() => {
expect(findCreateIncidentBtn().attributes('loading')).toBe('true');
});
});
});
}); });
...@@ -7,12 +7,17 @@ RSpec.describe Projects::IncidentsHelper do ...@@ -7,12 +7,17 @@ RSpec.describe Projects::IncidentsHelper do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:project_path) { project.full_path } let(:project_path) { project.full_path }
let(:new_issue_path) { new_project_issue_path(project) }
describe '#incidents_data' do describe '#incidents_data' do
subject(:data) { helper.incidents_data(project) } subject(:data) { helper.incidents_data(project) }
it 'returns frontend configuration' do it 'returns frontend configuration' do
expect(data).to match('project-path' => project_path) expect(data).to match(
'project-path' => project_path,
'new-issue-path' => new_issue_path,
'incident-template-name' => 'incident'
)
end end
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