Commit 51ea80b0 authored by Simon Knox's avatar Simon Knox

Merge branch '289921-add-copy-issue-url-button-to-vulnerability-error-message' into 'master'

Add copy issue URL button to vulnerability error message

See merge request gitlab-org/gitlab!76215
parents e0eb631f 46c207eb
...@@ -66,6 +66,11 @@ export default { ...@@ -66,6 +66,11 @@ export default {
required: false, required: false,
default: 'medium', default: 'medium',
}, },
variant: {
type: String,
required: false,
default: 'default',
},
}, },
computed: { computed: {
clipboardText() { clipboardText() {
...@@ -92,6 +97,7 @@ export default { ...@@ -92,6 +97,7 @@ export default {
:size="size" :size="size"
icon="copy-to-clipboard" icon="copy-to-clipboard"
:aria-label="__('Copy this value')" :aria-label="__('Copy this value')"
:variant="variant"
v-on="$listeners" v-on="$listeners"
> >
<slot></slot> <slot></slot>
......
-# We currently only support `alert`, `notice`, `success`, 'toast' -# We currently only support `alert`, `notice`, `success`, 'toast', and 'raw'
- icons = {'alert' => 'error', 'notice' => 'information-o', 'success' => 'check-circle'} - icons = {'alert' => 'error', 'notice' => 'information-o', 'success' => 'check-circle'}
.flash-container.flash-container-page.sticky{ data: { qa_selector: 'flash_container' } } .flash-container.flash-container-page.sticky{ data: { qa_selector: 'flash_container' } }
- flash.each do |key, value| - flash.each do |key, value|
- if key == 'toast' && value - if key == 'toast' && value
.js-toast-message{ data: { message: value } } .js-toast-message{ data: { message: value } }
- elsif key == 'raw' && value
= value
- elsif value == I18n.t('devise.failure.unconfirmed') - elsif value == I18n.t('devise.failure.unconfirmed')
= render 'shared/confirm_your_email_alert' = render 'shared/confirm_your_email_alert'
- elsif value - elsif value
......
<script>
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
components: { GlAlert, GlSprintf, GlLink, ClipboardButton },
props: {
vulnerabilityLink: {
type: String,
required: true,
},
},
data() {
return {
isVisible: true,
};
},
methods: {
hideAlert() {
this.isVisible = false;
},
},
i18n: {
alertTitle: __('Unable to create link to vulnerability'),
alertMessage: __(
'Manually link this issue by adding it to the linked issue section of the %{linkStart}originating vulnerability%{linkEnd}.',
),
clipboardButtonText: __('Copy issue URL to clipboard'),
},
currentUrl: window.location.href,
};
</script>
<template>
<gl-alert
v-if="isVisible"
variant="danger"
:title="$options.i18n.alertTitle"
class="gl-mt-4"
@dismiss="hideAlert"
>
<p>
<gl-sprintf :message="$options.i18n.alertMessage">
<template #link="{ content }">
<gl-link :href="vulnerabilityLink" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
<clipboard-button
:text="$options.currentUrl"
:title="$options.i18n.clipboardButtonText"
category="primary"
variant="confirm"
>
{{ $options.i18n.clipboardButtonText }}
</clipboard-button>
</gl-alert>
</template>
import Vue from 'vue';
import App from './components/unable_to_link_vulnerability_error.vue';
export default () => {
const el = document.querySelector('#js-unable-to-link-vulnerability');
if (!el) return null;
const { vulnerabilityLink } = el.dataset;
return new Vue({
el,
render: (h) =>
h(App, {
props: { vulnerabilityLink },
}),
});
};
...@@ -3,12 +3,14 @@ import { store } from '~/notes/stores'; ...@@ -3,12 +3,14 @@ import { store } from '~/notes/stores';
import initShow from '~/issues/show'; import initShow from '~/issues/show';
import initRelatedIssues from '~/related_issues'; import initRelatedIssues from '~/related_issues';
import initSidebarBundle from '~/sidebar/sidebar_bundle'; import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initUnableToLinkVulnerabilityError from 'ee/issues/init_unable_to_link_vulnerability_error';
import UserCallout from '~/user_callout'; import UserCallout from '~/user_callout';
initShow(); initShow();
initSidebarBundle(store); initSidebarBundle(store);
initRelatedIssues(); initRelatedIssues();
initRelatedFeatureFlags(); initRelatedFeatureFlags();
initUnableToLinkVulnerabilityError();
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new UserCallout({ className: 'js-epics-sidebar-callout' }); new UserCallout({ className: 'js-epics-sidebar-callout' });
......
...@@ -59,12 +59,7 @@ module EE ...@@ -59,12 +59,7 @@ module EE
vulnerability_issue_feedback_params(issue, vulnerability) vulnerability_issue_feedback_params(issue, vulnerability)
).execute ).execute
errors = [] flash[:raw] = render_vulnerability_link_alert.html_safe unless result[:message].errors.blank?
result[:message].full_messages.each do |error|
errors << render_vulnerability_link_alert(error)
end
flash[:alert] = errors.join('<br\>').html_safe unless errors.blank?
end end
def vulnerability def vulnerability
...@@ -103,12 +98,11 @@ module EE ...@@ -103,12 +98,11 @@ module EE
) )
end end
def render_vulnerability_link_alert(error_message) def render_vulnerability_link_alert
render_to_string( render_to_string(
partial: 'vulnerabilities/unable_to_link_vulnerability', partial: 'vulnerabilities/unable_to_link_vulnerability',
locals: { locals: {
vulnerability_link: vulnerability_path(vulnerability), vulnerability_link: vulnerability_path(vulnerability)
error_message: error_message
} }
) )
end end
......
%span.gl-alert-title #js-unable-to-link-vulnerability{ data: { vulnerability_link: vulnerability_link } }
= _('Unable to create link to vulnerability')
.gl-alert-body
= error_message
%br
- originating_vulnerability_link = link_to _('originating vulnerability'), vulnerability_link
= _('Manually link this issue by adding it to the linked issue section of the %{originating_vulnerability}.').html_safe % { originating_vulnerability: originating_vulnerability_link }
...@@ -137,7 +137,9 @@ RSpec.describe Projects::IssuesController do ...@@ -137,7 +137,9 @@ RSpec.describe Projects::IssuesController do
it 'shows an error message' do it 'shows an error message' do
send_request send_request
expect(flash[:alert]).to include('Unable to create link to vulnerability') expect(flash[:raw]).to include('id="js-unable-to-link-vulnerability"')
expect(flash[:raw]).to include("data-vulnerability-link=\"/#{namespace.path}/#{project.path}/-/security/vulnerabilities/#{vulnerabilities_issue_link.vulnerability.id}\"")
expect(vulnerability.issue_links.map(&:issue)).to eq([vulnerabilities_issue_link.issue]) expect(vulnerability.issue_links.map(&:issue)).to eq([vulnerabilities_issue_link.issue])
end end
end end
......
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import UnableToLinkVulnerabilityError from 'ee/issues/components/unable_to_link_vulnerability_error.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
describe('Unable To Link Vulnerability Error component', () => {
let wrapper;
const findAlert = () => wrapper.findComponent(GlAlert);
const createWrapper = ({ vulnerabilityLink = '' } = {}) => {
wrapper = shallowMount(UnableToLinkVulnerabilityError, {
propsData: {
vulnerabilityLink,
},
stubs: { GlSprintf },
});
};
afterEach(() => {
wrapper.destroy();
});
it('shows the vulnerability link', () => {
const vulnerabilityLink = 'https://gitlab.com';
createWrapper({ vulnerabilityLink });
expect(wrapper.html()).toContain(vulnerabilityLink);
});
it('hides the error when the alert is dismissed', async () => {
createWrapper();
findAlert().vm.$emit('dismiss');
await nextTick();
expect(findAlert().exists()).toBe(false);
});
it('passes the expected props to the ClipboardButton component', () => {
createWrapper();
expect(wrapper.findComponent(ClipboardButton).props()).toMatchObject({
category: 'primary',
variant: 'confirm',
text: window.location.href,
});
});
});
...@@ -9664,6 +9664,9 @@ msgstr "" ...@@ -9664,6 +9664,9 @@ msgstr ""
msgid "Copy file path" msgid "Copy file path"
msgstr "" msgstr ""
msgid "Copy issue URL to clipboard"
msgstr ""
msgid "Copy key" msgid "Copy key"
msgstr "" msgstr ""
...@@ -21528,7 +21531,7 @@ msgstr "" ...@@ -21528,7 +21531,7 @@ msgstr ""
msgid "ManualOrdering|Couldn't save the order of the issues" msgid "ManualOrdering|Couldn't save the order of the issues"
msgstr "" msgstr ""
msgid "Manually link this issue by adding it to the linked issue section of the %{originating_vulnerability}." msgid "Manually link this issue by adding it to the linked issue section of the %{linkStart}originating vulnerability%{linkEnd}."
msgstr "" msgstr ""
msgid "Map a FogBugz account ID to a GitLab user" msgid "Map a FogBugz account ID to a GitLab user"
...@@ -42438,9 +42441,6 @@ msgstr "" ...@@ -42438,9 +42441,6 @@ msgstr ""
msgid "or" msgid "or"
msgstr "" msgstr ""
msgid "originating vulnerability"
msgstr ""
msgid "other card matches" msgid "other card matches"
msgstr "" msgstr ""
......
...@@ -34,6 +34,7 @@ exports[`Blob Header Filepath rendering matches the snapshot 1`] = ` ...@@ -34,6 +34,7 @@ exports[`Blob Header Filepath rendering matches the snapshot 1`] = `
text="foo/bar/dummy.md" text="foo/bar/dummy.md"
title="Copy file path" title="Copy file path"
tooltipplacement="top" tooltipplacement="top"
variant="default"
/> />
</div> </div>
`; `;
...@@ -37,6 +37,7 @@ exports[`publish_method renders 1`] = ` ...@@ -37,6 +37,7 @@ exports[`publish_method renders 1`] = `
text="b83d6e391c22777fca1ed3012fce84f633d7fed0" text="b83d6e391c22777fca1ed3012fce84f633d7fed0"
title="Copy commit SHA" title="Copy commit SHA"
tooltipplacement="top" tooltipplacement="top"
variant="default"
/> />
</div> </div>
`; `;
...@@ -37,6 +37,7 @@ exports[`publish_method renders 1`] = ` ...@@ -37,6 +37,7 @@ exports[`publish_method renders 1`] = `
text="sha-baz" text="sha-baz"
title="Copy commit SHA" title="Copy commit SHA"
tooltipplacement="top" tooltipplacement="top"
variant="default"
/> />
</div> </div>
`; `;
...@@ -99,6 +99,7 @@ exports[`Repository last commit component renders commit widget 1`] = ` ...@@ -99,6 +99,7 @@ exports[`Repository last commit component renders commit widget 1`] = `
text="123456789" text="123456789"
title="Copy commit SHA" title="Copy commit SHA"
tooltipplacement="top" tooltipplacement="top"
variant="default"
/> />
</gl-button-group-stub> </gl-button-group-stub>
</div> </div>
...@@ -209,6 +210,7 @@ exports[`Repository last commit component renders the signature HTML as returned ...@@ -209,6 +210,7 @@ exports[`Repository last commit component renders the signature HTML as returned
text="123456789" text="123456789"
title="Copy commit SHA" title="Copy commit SHA"
tooltipplacement="top" tooltipplacement="top"
variant="default"
/> />
</gl-button-group-stub> </gl-button-group-stub>
</div> </div>
......
...@@ -89,6 +89,16 @@ describe('clipboard button', () => { ...@@ -89,6 +89,16 @@ describe('clipboard button', () => {
expect(onClick).toHaveBeenCalled(); expect(onClick).toHaveBeenCalled();
}); });
it('passes the category and variant props to the GlButton', () => {
const category = 'tertiary';
const variant = 'confirm';
createWrapper({ title: '', text: '', category, variant });
expect(findButton().props('category')).toBe(category);
expect(findButton().props('variant')).toBe(variant);
});
describe('integration', () => { describe('integration', () => {
it('actually copies to clipboard', () => { it('actually copies to clipboard', () => {
initCopyToClipboard(); initCopyToClipboard();
......
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