Commit 27fb2497 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '293709-convert-epics-autocomplete-from-at-js-to-tribute' into 'master'

Convert quick actions autocomplete from at.js to tribute

See merge request gitlab-org/gitlab!49969
parents a99f075f c0919c82
<script>
import updateMixin from '../../mixins/update';
import markdownField from '../../../vue_shared/components/markdown/field.vue';
import markdownField from '~/vue_shared/components/markdown/field.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
markdownField,
},
mixins: [updateMixin],
mixins: [glFeatureFlagsMixin(), updateMixin],
props: {
formState: {
type: Object,
......@@ -55,7 +56,7 @@ export default {
class="note-textarea js-gfm-input js-autosize markdown-area
qa-description-textarea"
dir="auto"
data-supports-quick-actions="true"
:data-supports-quick-actions="!glFeatures.tributeAutocomplete"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
@keydown.meta.enter="updateIssuable"
......
......@@ -20,6 +20,7 @@ import eventHub from '../event_hub';
import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
import markdownField from '~/vue_shared/components/markdown/field.vue';
import userAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue';
import issuableStateMixin from '../mixins/issuable_state';
......@@ -36,7 +37,7 @@ export default {
TimelineEntryItem,
GlIcon,
},
mixins: [issuableStateMixin],
mixins: [glFeatureFlagsMixin(), issuableStateMixin],
props: {
noteableType: {
type: String,
......@@ -339,7 +340,7 @@ export default {
class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area"
data-qa-selector="comment_field"
data-testid="comment-field"
data-supports-quick-actions="true"
:data-supports-quick-actions="!glFeatures.tributeAutocomplete"
:aria-label="__('Description')"
:placeholder="__('Write a comment or drag your files here…')"
@keydown.up="editCurrentUserLastNote()"
......
......@@ -3,8 +3,9 @@
import { mapGetters, mapActions, mapState } from 'vuex';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
import NoteableWarning from '../../vue_shared/components/notes/noteable_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue';
import markdownField from '~/vue_shared/components/markdown/field.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
import { __, sprintf } from '~/locale';
......@@ -16,7 +17,7 @@ export default {
NoteableWarning,
markdownField,
},
mixins: [issuableStateMixin, resolvable],
mixins: [glFeatureFlagsMixin(), issuableStateMixin, resolvable],
props: {
noteBody: {
type: String,
......@@ -342,7 +343,7 @@ export default {
ref="textarea"
slot="textarea"
v-model="updatedNoteBody"
:data-supports-quick-actions="!isEditing"
:data-supports-quick-actions="!isEditing && !glFeatures.tributeAutocomplete"
name="note[note]"
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form"
data-qa-selector="reply_field"
......
......@@ -13,6 +13,7 @@ export const GfmAutocompleteType = {
Members: 'members',
MergeRequests: 'mergeRequests',
Milestones: 'milestones',
QuickActions: 'commands',
Snippets: 'snippets',
};
......@@ -142,6 +143,34 @@ export const tributeConfig = {
},
},
[GfmAutocompleteType.QuickActions]: {
config: {
trigger: '/',
fillAttr: 'name',
lookup: value => `${value.name}${value.aliases.join()}`,
menuItemTemplate: ({ original }) => {
const aliases = original.aliases.length
? `<small>(or /${original.aliases.join(', /')})</small>`
: '';
const params = original.params.length ? `<small>${original.params.join(' ')}</small>` : '';
let description = '';
if (original.warning) {
const confidentialIcon =
original.icon === 'confidential' ? spriteIcon('eye-slash', 's16 gl-mr-2') : '';
description = `<small>${confidentialIcon}<em>${original.warning}</em></small>`;
} else if (original.description) {
description = `<small><em>${original.description}</em></small>`;
}
return `<div>/${original.name} ${aliases} ${params}</div>
<div>${description}</div>`;
},
},
},
[GfmAutocompleteType.Snippets]: {
config: {
trigger: '$',
......
......@@ -62,16 +62,15 @@ RSpec.describe 'GFM autocomplete EE', :js do
it 'only lists users who are currently assigned to the issue when using /unassign' do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/una')
note.native.send_keys('/unassign ')
# The `/unassign` ajax response might replace the one by `@` below causing a failed test
# so we need to wait for the `/assign` ajax request to finish first
wait_for_requests
note.native.send_keys('@')
wait_for_requests
end
find('.atwho-view li', text: '/unassign')
note.native.send_keys(:tab)
note.native.send_keys(:right)
wait_for_requests
users = find('.tribute-container ul')
users = find('.tribute-container ul', visible: true)
expect(users).to have_content(user.username)
expect(users).not_to have_content(another_user.username)
end
......
......@@ -723,20 +723,15 @@ RSpec.describe 'GFM autocomplete', :js do
expect(page).not_to have_selector('.tribute-container')
end
it 'triggers autocomplete after selecting a quick action' do
it 'autocompletes for quick actions' do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/as')
wait_for_requests
note.native.send_keys(:tab)
end
find('.atwho-view li', text: '/assign')
note.native.send_keys(:tab)
note.native.send_keys(:right)
wait_for_requests
user_item = find('.tribute-container ul', text: user.username, visible: true)
expect(user_item).to have_content(user.username)
expect(note.value).to have_text('/assign')
end
end
......@@ -755,15 +750,14 @@ RSpec.describe 'GFM autocomplete', :js do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/as')
note.native.send_keys('/assign ')
# The `/assign` ajax response might replace the one by `@` below causing a failed test
# so we need to wait for the `/assign` ajax request to finish first
wait_for_requests
note.native.send_keys('@')
wait_for_requests
end
find('.atwho-view li', text: '/assign')
note.native.send_keys(:tab)
note.native.send_keys(:right)
wait_for_requests
expect(find('.tribute-container ul', visible: true)).not_to have_content(user.username)
expect(find('.tribute-container ul', visible: true)).to have_content(unassigned_user.username)
end
......@@ -775,12 +769,14 @@ RSpec.describe 'GFM autocomplete', :js do
page.within '.timeline-content-form' do
note.native.send_keys('/assign @user2')
note.native.send_keys(:enter)
note.native.send_keys('/assign @')
note.native.send_keys(:right)
note.native.send_keys('/assign ')
# The `/assign` ajax response might replace the one by `@` below causing a failed test
# so we need to wait for the `/assign` ajax request to finish first
wait_for_requests
note.native.send_keys('@')
wait_for_requests
end
wait_for_requests
expect(find('.tribute-container ul', visible: true)).not_to have_content(user.username)
expect(find('.tribute-container ul', visible: true)).to have_content(unassigned_user.username)
end
......
......@@ -10,6 +10,7 @@ RSpec.describe "User comments on issue", :js do
let(:user) { create(:user) }
before do
stub_feature_flags(tribute_autocomplete: false)
project.add_guest(user)
sign_in(user)
......
......@@ -52,4 +52,9 @@ exports[`gfm_autocomplete/utils merge requests config shows the reference and ti
exports[`gfm_autocomplete/utils milestones config shows the title in the menu item 1`] = `"13.2 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
exports[`gfm_autocomplete/utils quick actions config shows the name, aliases, params and description in the menu item 1`] = `
"<div>/unlabel <small>(or /remove_label)</small> <small>~label1 ~\\"label 2\\"</small></div>
<div><small><em>Remove all or specific label(s)</em></small></div>"
`;
exports[`gfm_autocomplete/utils snippets config shows the id and title in the menu item 1`] = `"<small>123456</small> Snippet title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
......@@ -339,6 +339,36 @@ describe('gfm_autocomplete/utils', () => {
});
});
describe('quick actions config', () => {
const quickActionsConfig = tributeConfig[GfmAutocompleteType.QuickActions].config;
const quickAction = {
name: 'unlabel',
aliases: ['remove_label'],
description: 'Remove all or specific label(s)',
warning: '',
icon: '',
params: ['~label1 ~"label 2"'],
};
it('uses / as the trigger', () => {
expect(quickActionsConfig.trigger).toBe('/');
});
it('inserts the name on autocomplete selection', () => {
expect(quickActionsConfig.fillAttr).toBe('name');
});
it('searches using both the name and aliases', () => {
expect(quickActionsConfig.lookup(quickAction)).toBe(
`${quickAction.name}${quickAction.aliases.join(', /')}`,
);
});
it('shows the name, aliases, params and description in the menu item', () => {
expect(quickActionsConfig.menuItemTemplate({ original: quickAction })).toMatchSnapshot();
});
});
describe('snippets config', () => {
const snippetsConfig = tributeConfig[GfmAutocompleteType.Snippets].config;
const snippet = {
......
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