Resolve "Drop down filter for project snippets"

This commit is contained in:
Fabian Schneider 2018-10-05 09:42:38 +00:00 committed by Fatih Acet
parent 16d038da1c
commit b55c320c89
13 changed files with 72 additions and 8 deletions

View file

@ -15,6 +15,7 @@ export const defaultAutocompleteConfig = {
epics: true, epics: true,
milestones: true, milestones: true,
labels: true, labels: true,
snippets: true,
}; };
class GfmAutoComplete { class GfmAutoComplete {
@ -50,6 +51,7 @@ class GfmAutoComplete {
if (this.enableMap.milestones) this.setupMilestones($input); if (this.enableMap.milestones) this.setupMilestones($input);
if (this.enableMap.mergeRequests) this.setupMergeRequests($input); if (this.enableMap.mergeRequests) this.setupMergeRequests($input);
if (this.enableMap.labels) this.setupLabels($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 // We don't instantiate the quick actions autocomplete for note and issue/MR edit forms
$input.filter('[data-supports-quick-actions="true"]').atwho({ $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() { getDefaultCallbacks() {
const fetchData = this.fetchData.bind(this); const fetchData = this.fetchData.bind(this);
@ -470,7 +505,7 @@ class GfmAutoComplete {
// The below is taken from At.js source // 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 // Tweaked to commands to start without a space only if char before is a non-word character
// https://github.com/ichord/At.js // 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 atSymbolsWithoutBar = Object.keys(controllers).join('');
const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop(); const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop();
const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
@ -497,6 +532,7 @@ GfmAutoComplete.atTypeMap = {
'~': 'labels', '~': 'labels',
'%': 'milestones', '%': 'milestones',
'/': 'commands', '/': 'commands',
$: 'snippets',
}; };
// Emoji // Emoji
@ -519,7 +555,7 @@ GfmAutoComplete.Labels = {
// eslint-disable-next-line no-template-curly-in-string // eslint-disable-next-line no-template-curly-in-string
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>', template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>',
}; };
// Issues and MergeRequests // Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = { GfmAutoComplete.Issues = {
// eslint-disable-next-line no-template-curly-in-string // eslint-disable-next-line no-template-curly-in-string
template: '<li><small>${id}</small> ${title}</li>', template: '<li><small>${id}</small> ${title}</li>',

View file

@ -11,6 +11,7 @@ export default () => {
epics: false, epics: false,
milestones: false, milestones: false,
labels: false, labels: false,
snippets: false,
}); });
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
}; };

View file

@ -15,5 +15,6 @@ export default (initGFM = true) => {
epics: initGFM, epics: initGFM,
milestones: initGFM, milestones: initGFM,
labels: initGFM, labels: initGFM,
snippets: initGFM,
}); });
}; };

View file

@ -76,6 +76,7 @@
epics: this.enableAutocomplete, epics: this.enableAutocomplete,
milestones: this.enableAutocomplete, milestones: this.enableAutocomplete,
labels: this.enableAutocomplete, labels: this.enableAutocomplete,
snippets: this.enableAutocomplete,
}); });
}, },
beforeDestroy() { beforeDestroy() {

View file

@ -27,6 +27,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
render json: @autocomplete_service.commands(target, params[:type]) render json: @autocomplete_service.commands(target, params[:type])
end end
def snippets
render json: @autocomplete_service.snippets
end
private private
def load_autocomplete_service def load_autocomplete_service

View file

@ -292,7 +292,8 @@ module ApplicationHelper
mergeRequests: merge_requests_project_autocomplete_sources_path(object), mergeRequests: merge_requests_project_autocomplete_sources_path(object),
labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
milestones: milestones_project_autocomplete_sources_path(object), 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
end end

View file

@ -29,6 +29,10 @@ module Projects
QuickActions::InterpretService.new(project, current_user).available_commands(noteable) QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
end end
def snippets
SnippetsFinder.new(current_user, project: project).execute.select([:id, :title])
end
def labels_as_hash(target) def labels_as_hash(target)
super(target, project_id: project.id, include_ancestor_groups: true) super(target, project_id: project.id, include_ancestor_groups: true)
end end

View file

@ -0,0 +1,5 @@
---
title: Add autocomplete drop down filter for project snippets
merge_request: 21458
author: Fabian Schneider
type: added

View file

@ -32,6 +32,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'labels' get 'labels'
get 'milestones' get 'milestones'
get 'commands' get 'commands'
get 'snippets'
end end
end end

View file

@ -5,6 +5,7 @@ describe 'GFM autocomplete', :js do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:label) { create(:label, project: project, title: 'special+') } let(:label) { create(:label, project: project, title: 'special+') }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let!(:project_snippet) { create(:project_snippet, project: project, title: 'code snippet') }
before do before do
project.add_maintainer(user) project.add_maintainer(user)
@ -301,6 +302,16 @@ describe 'GFM autocomplete', :js do
end end
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 private
def expect_to_wrap(should_wrap, item, note, value) def expect_to_wrap(should_wrap, item, note, value)

View file

@ -174,9 +174,7 @@ describe ApplicationHelper do
it 'returns paths for autocomplete_sources_controller' do it 'returns paths for autocomplete_sources_controller' do
sources = helper.autocomplete_data_sources(project, noteable_type) sources = helper.autocomplete_data_sources(project, noteable_type)
expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets])
expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands])
sources.keys.each do |key| sources.keys.each do |key|
expect(sources[key]).not_to be_nil expect(sources[key]).not_to be_nil
end end

View file

@ -103,7 +103,7 @@ describe('GfmAutoComplete', function () {
gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext) gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext)
); );
const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%']; const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%', '$'];
const otherFlags = ['/', ':']; const otherFlags = ['/', ':'];
const flags = flagsUseDefaultMatcher.concat(otherFlags); const flags = flagsUseDefaultMatcher.concat(otherFlags);

View file

@ -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 # 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 # 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 # 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 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 it "to ##{action}" do
expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq') expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq')
end end