From b55c320c89b939718562f9d6a606e831cbb776c4 Mon Sep 17 00:00:00 2001 From: Fabian Schneider Date: Fri, 5 Oct 2018 09:42:38 +0000 Subject: [PATCH] Resolve "Drop down filter for project snippets" --- app/assets/javascripts/gfm_auto_complete.js | 40 ++++++++++++++++++- app/assets/javascripts/pages/snippets/form.js | 1 + .../javascripts/shared/milestones/form.js | 1 + .../vue_shared/components/markdown/field.vue | 1 + .../autocomplete_sources_controller.rb | 4 ++ app/helpers/application_helper.rb | 3 +- app/services/projects/autocomplete_service.rb | 4 ++ ...-drop-down-filter-for-project-snippets.yml | 5 +++ config/routes/project.rb | 1 + spec/features/issues/gfm_autocomplete_spec.rb | 11 +++++ spec/helpers/application_helper_spec.rb | 4 +- spec/javascripts/gfm_auto_complete_spec.js | 2 +- spec/routing/project_routing_spec.rb | 3 +- 13 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 73b2cd0b2c7..95636a9ccdd 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -15,6 +15,7 @@ export const defaultAutocompleteConfig = { epics: true, milestones: true, labels: true, + snippets: true, }; class GfmAutoComplete { @@ -50,6 +51,7 @@ class GfmAutoComplete { if (this.enableMap.milestones) this.setupMilestones($input); if (this.enableMap.mergeRequests) this.setupMergeRequests($input); if (this.enableMap.labels) this.setupLabels($input); + if (this.enableMap.snippets) this.setupSnippets($input); // We don't instantiate the quick actions autocomplete for note and issue/MR edit forms $input.filter('[data-supports-quick-actions="true"]').atwho({ @@ -360,6 +362,39 @@ class GfmAutoComplete { }); } + setupSnippets($input) { + $input.atwho({ + at: '$', + alias: 'snippets', + searchKey: 'search', + displayTpl(value) { + let tmpl = GfmAutoComplete.Loading.template; + if (value.title != null) { + tmpl = GfmAutoComplete.Issues.template; + } + return tmpl; + }, + data: GfmAutoComplete.defaultLoadingData, + // eslint-disable-next-line no-template-curly-in-string + insertTpl: '${atwho-at}${id}', + callbacks: { + ...this.getDefaultCallbacks(), + beforeSave(snippets) { + return $.map(snippets, (m) => { + if (m.title == null) { + return m; + } + return { + id: m.id, + title: sanitize(m.title), + search: `${m.id} ${m.title}`, + }; + }); + }, + }, + }); + } + getDefaultCallbacks() { const fetchData = this.fetchData.bind(this); @@ -470,7 +505,7 @@ class GfmAutoComplete { // The below is taken from At.js source // Tweaked to commands to start without a space only if char before is a non-word character // https://github.com/ichord/At.js - const atSymbolsWithBar = Object.keys(controllers).join('|'); + const atSymbolsWithBar = Object.keys(controllers).join('|').replace(/[$]/, '\\$&'); const atSymbolsWithoutBar = Object.keys(controllers).join(''); const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop(); const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); @@ -497,6 +532,7 @@ GfmAutoComplete.atTypeMap = { '~': 'labels', '%': 'milestones', '/': 'commands', + $: 'snippets', }; // Emoji @@ -519,7 +555,7 @@ GfmAutoComplete.Labels = { // eslint-disable-next-line no-template-curly-in-string template: '
  • ${title}
  • ', }; -// Issues and MergeRequests +// Issues, MergeRequests and Snippets GfmAutoComplete.Issues = { // eslint-disable-next-line no-template-curly-in-string template: '
  • ${id} ${title}
  • ', diff --git a/app/assets/javascripts/pages/snippets/form.js b/app/assets/javascripts/pages/snippets/form.js index f369c7ef9a6..8859557e62d 100644 --- a/app/assets/javascripts/pages/snippets/form.js +++ b/app/assets/javascripts/pages/snippets/form.js @@ -11,6 +11,7 @@ export default () => { epics: false, milestones: false, labels: false, + snippets: false, }); new ZenMode(); // eslint-disable-line no-new }; diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js index 8681a1776c6..0ff84dc4667 100644 --- a/app/assets/javascripts/shared/milestones/form.js +++ b/app/assets/javascripts/shared/milestones/form.js @@ -15,5 +15,6 @@ export default (initGFM = true) => { epics: initGFM, milestones: initGFM, labels: initGFM, + snippets: initGFM, }); }; diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index d62537021ca..10e8ddad9cd 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -76,6 +76,7 @@ epics: this.enableAutocomplete, milestones: this.enableAutocomplete, labels: this.enableAutocomplete, + snippets: this.enableAutocomplete, }); }, beforeDestroy() { diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb index 7c93cf36862..d386fb63d9f 100644 --- a/app/controllers/projects/autocomplete_sources_controller.rb +++ b/app/controllers/projects/autocomplete_sources_controller.rb @@ -27,6 +27,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController render json: @autocomplete_service.commands(target, params[:type]) end + def snippets + render json: @autocomplete_service.snippets + end + private def load_autocomplete_service diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 32fc8e5e9ce..4f91e3e4117 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -292,7 +292,8 @@ module ApplicationHelper mergeRequests: merge_requests_project_autocomplete_sources_path(object), labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), milestones: milestones_project_autocomplete_sources_path(object), - commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]) + commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), + snippets: snippets_project_autocomplete_sources_path(object) } end end diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 7b747171d9c..61f6402a810 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -29,6 +29,10 @@ module Projects QuickActions::InterpretService.new(project, current_user).available_commands(noteable) end + def snippets + SnippetsFinder.new(current_user, project: project).execute.select([:id, :title]) + end + def labels_as_hash(target) super(target, project_id: project.id, include_ancestor_groups: true) end diff --git a/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml b/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml new file mode 100644 index 00000000000..e24c55e3bad --- /dev/null +++ b/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml @@ -0,0 +1,5 @@ +--- +title: Add autocomplete drop down filter for project snippets +merge_request: 21458 +author: Fabian Schneider +type: added diff --git a/config/routes/project.rb b/config/routes/project.rb index 5236962b999..3c8d4458fba 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -32,6 +32,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get 'labels' get 'milestones' get 'commands' + get 'snippets' end end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 98e37d8011a..08bf9bc7243 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -5,6 +5,7 @@ describe 'GFM autocomplete', :js do let(:project) { create(:project) } let(:label) { create(:label, project: project, title: 'special+') } let(:issue) { create(:issue, project: project) } + let!(:project_snippet) { create(:project_snippet, project: project, title: 'code snippet') } before do project.add_maintainer(user) @@ -301,6 +302,16 @@ describe 'GFM autocomplete', :js do end end + it 'shows project snippets' do + page.within '.timeline-content-form' do + find('#note-body').native.send_keys('$') + end + + page.within '.atwho-container' do + expect(page).to have_content(project_snippet.title) + end + end + private def expect_to_wrap(should_wrap, item, note, value) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 1238cfbd1e7..4135f31e051 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -174,9 +174,7 @@ describe ApplicationHelper do it 'returns paths for autocomplete_sources_controller' do sources = helper.autocomplete_data_sources(project, noteable_type) - - expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands]) - + expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets]) sources.keys.each do |key| expect(sources[key]).not_to be_nil end diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js index 4f9cacf2724..b57c4943c01 100644 --- a/spec/javascripts/gfm_auto_complete_spec.js +++ b/spec/javascripts/gfm_auto_complete_spec.js @@ -103,7 +103,7 @@ describe('GfmAutoComplete', function () { gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext) ); - const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%']; + const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%', '$']; const otherFlags = ['/', ':']; const flags = flagsUseDefaultMatcher.concat(otherFlags); diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 56df8dddbc1..bdfb12dc5df 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -133,8 +133,9 @@ describe 'project routing' do # labels_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/labels(.:format) projects/autocomplete_sources#labels # milestones_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/milestones(.:format) projects/autocomplete_sources#milestones # commands_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/commands(.:format) projects/autocomplete_sources#commands + # snippets_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/snippets(.:format) projects/autocomplete_sources#snippets describe Projects::AutocompleteSourcesController, 'routing' do - [:members, :issues, :merge_requests, :labels, :milestones, :commands].each do |action| + [:members, :issues, :merge_requests, :labels, :milestones, :commands, :snippets].each do |action| it "to ##{action}" do expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq') end