Commit 75044a7f authored by Brett Walker's avatar Brett Walker Committed by sstern

Add copy email to issue sidebar

Add ability for a user to copy email
to clipboard from the sidebar
parent cf4abe87
<script>
import { s__, __, sprintf } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
i18n: {
copyEmail: __('Copy email address'),
},
components: {
ClipboardButton,
},
props: {
copyText: {
type: String,
required: true,
},
},
computed: {
emailText() {
return sprintf(s__('RightSidebar|Issue email: %{copyText}'), { copyText: this.copyText });
},
},
};
</script>
<template>
<div
data-qa-selector="copy-forward-email"
class="copy-email-address gl-display-flex gl-align-items-center gl-justify-content-space-between"
>
<span
class="gl-overflow-hidden gl-text-overflow-ellipsis gl-white-space-nowrap hide-collapsed gl-w-85p"
>{{ emailText }}</span
>
<clipboard-button
class="copy-email-button gl-bg-none!"
category="tertiary"
:title="$options.i18n.copyEmail"
:text="copyText"
tooltip-placement="left"
/>
</div>
</template>
......@@ -12,6 +12,7 @@ import sidebarParticipants from './components/participants/sidebar_participants.
import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptions.vue';
import SidebarSeverity from './components/severity/sidebar_severity.vue';
import Translate from '../vue_shared/translate';
import CopyEmailToClipboard from './components/copy_email_to_clipboard.vue';
import createDefaultClient from '~/lib/graphql';
import { isInIssuePage, isInIncidentPage, parseBoolean } from '~/lib/utils/common_utils';
import createFlash from '~/flash';
......@@ -272,6 +273,21 @@ function mountSeverityComponent() {
});
}
function mountCopyEmailComponent() {
const el = document.getElementById('issuable-copy-email');
if (!el) return;
const { createNoteEmail } = getSidebarOptions();
// eslint-disable-next-line no-new
new Vue({
el,
render: (createElement) =>
createElement(CopyEmailToClipboard, { props: { copyText: createNoteEmail } }),
});
}
export function mountSidebar(mediator) {
mountAssigneesComponent(mediator);
mountReviewersComponent(mediator);
......@@ -279,6 +295,7 @@ export function mountSidebar(mediator) {
mountLockComponent();
mountParticipantsComponent(mediator);
mountSubscriptionsComponent(mediator);
mountCopyEmailComponent();
new SidebarMoveIssue(
mediator,
......
......@@ -58,6 +58,19 @@
height: $gl-padding;
}
}
.copy-email-button { // TODO: replace with utility
@include gl-w-full;
@include gl-h-full;
}
.copy-email-address {
height: 60px;
&:hover {
background: $gray-100;
}
}
}
.right-sidebar-expanded {
......
......@@ -2,6 +2,7 @@
This should be removed when this sidebar is converted to Vue since assignee data is also available in the `issuable_sidebar` hash
- issuable_type = issuable_sidebar[:type]
- show_forwarding_email = !issuable_sidebar[:create_note_email].nil?
- signed_in = !!issuable_sidebar.dig(:current_user, :id)
- can_edit_issuable = issuable_sidebar.dig(:current_user, :can_edit)
- add_page_startup_api_call "#{issuable_sidebar[:issuable_json_path]}?serializer=sidebar_extras"
......@@ -145,6 +146,9 @@
= _('Source branch: %{source_branch_open}%{source_branch}%{source_branch_close}').html_safe % { source_branch_open: "<cite class='ref-name' title='#{source_branch}'>".html_safe, source_branch_close: "</cite>".html_safe, source_branch: source_branch }
= clipboard_button(text: source_branch, title: _('Copy branch name'), placement: "left", boundary: 'viewport')
- if show_forwarding_email
.block
#issuable-copy-email
- if issuable_sidebar.dig(:current_user, :can_move)
.block.js-sidebar-move-issue-block
.sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') }
......
---
title: Add copy email to issue sidebar
merge_request: 50127
author:
type: added
......@@ -35,6 +35,7 @@ The numbers in the image correspond to the following features:
- **12.** [Participants](#participants)
- **13.** [Notifications](#notifications)
- **14.** [Reference](#reference)
- [Issue email](#email)
- **15.** [Edit](#edit)
- **16.** [Description](#description)
- **17.** [Mentions](#mentions)
......@@ -174,6 +175,12 @@ for the issue. Notifications are automatically enabled after you participate in
`foo/bar#xxx`, where `foo` is the `username` or `groupname`, `bar` is the
`project-name`, and `xxx` is the issue number.
### Email
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18816) in GitLab 13.8.
Guest users can see a button to copy the email address for the issue. Sending an email to this address creates a comment containing the email body.
### Edit
Clicking this icon opens the issue for editing. All the fields which
......
......@@ -7899,6 +7899,9 @@ msgstr ""
msgid "Copy commit SHA"
msgstr ""
msgid "Copy email address"
msgstr ""
msgid "Copy environment"
msgstr ""
......@@ -24309,6 +24312,9 @@ msgstr ""
msgid "Revoked project access token %{project_access_token_name}!"
msgstr ""
msgid "RightSidebar|Issue email: %{copyText}"
msgstr ""
msgid "RightSidebar|adding a"
msgstr ""
......
......@@ -12,6 +12,11 @@ RSpec.describe 'Issue Sidebar' do
let(:issue) { create(:labeled_issue, project: project, labels: [label]) }
let!(:xss_label) { create(:label, project: project, title: '&lt;script&gt;alert("xss");&lt;&#x2F;script&gt;') }
before do
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
end
context 'when signed in' do
before do
sign_in(user)
end
......@@ -243,6 +248,12 @@ RSpec.describe 'Issue Sidebar' do
expect(page).not_to have_selector('.block.labels .js-sidebar-dropdown-toggle')
end
context 'sidebar', :js do
it 'finds issue copy forwarding email' do
expect(find('[data-qa-selector="copy-forward-email"]').text).to eq "Issue email: #{issue.creatable_note_email_address(user)}"
end
end
context 'interacting with collapsed sidebar', :js do
collapsed_sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed'
expanded_sidebar_selector = 'aside.right-sidebar.right-sidebar-expanded'
......@@ -266,6 +277,19 @@ RSpec.describe 'Issue Sidebar' do
end
end
end
end
context 'when not signed in' do
context 'sidebar', :js do
before do
visit_issue(project, issue)
end
it 'does not find issue email' do
expect(page).not_to have_selector('[data-qa-selector="copy-forward-email"]')
end
end
end
def visit_issue(project, issue)
visit project_issue_path(project, issue)
......
import { mount } from '@vue/test-utils';
import { getByText } from '@testing-library/dom';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CopyEmailToClipboard from '~/sidebar/components/copy_email_to_clipboard.vue';
describe('CopyEmailToClipboard component', () => {
const sampleEmail = 'sample+email@test.com';
const wrapper = mount(CopyEmailToClipboard, {
propsData: {
copyText: sampleEmail,
},
});
it('renders the Issue email text with the forwardable email', () => {
expect(getByText(wrapper.element, `Issue email: ${sampleEmail}`)).not.toBeNull();
});
it('finds ClipboardButton with the correct props', () => {
expect(wrapper.find(ClipboardButton).props('text')).toBe(sampleEmail);
});
});
import { mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import initCopyToClipboard from '~/behaviors/copy_to_clipboard';
describe('clipboard button', () => {
let wrapper;
......@@ -87,4 +88,25 @@ describe('clipboard button', () => {
expect(onClick).toHaveBeenCalled();
});
describe('integration', () => {
it('actually copies to clipboard', () => {
initCopyToClipboard();
document.execCommand = () => {};
jest.spyOn(document, 'execCommand').mockImplementation(() => true);
createWrapper(
{
text: 'copy me',
title: 'Copy this value',
},
{ attachTo: document.body },
);
findButton().trigger('click');
expect(document.execCommand).toHaveBeenCalledWith('copy');
});
});
});
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