Commit fff3b3aa authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '264441-revert-special-references-for-vulnerabilities-2' into 'master'

Revert Enable Special References for Vulnerabilities

See merge request gitlab-org/gitlab!45284
parents d983ad6a 1e79c80e
...@@ -14,7 +14,6 @@ export default function initGFMInput($els) { ...@@ -14,7 +14,6 @@ export default function initGFMInput($els) {
milestones: enableGFM, milestones: enableGFM,
mergeRequests: enableGFM, mergeRequests: enableGFM,
labels: enableGFM, labels: enableGFM,
vulnerabilities: enableGFM,
}); });
}); });
} }
...@@ -4,7 +4,6 @@ import { escape, template } from 'lodash'; ...@@ -4,7 +4,6 @@ import { escape, template } from 'lodash';
import SidebarMediator from '~/sidebar/sidebar_mediator'; import SidebarMediator from '~/sidebar/sidebar_mediator';
import glRegexp from './lib/utils/regexp'; import glRegexp from './lib/utils/regexp';
import AjaxCache from './lib/utils/ajax_cache'; import AjaxCache from './lib/utils/ajax_cache';
import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from './lib/utils/common_utils'; import { spriteIcon } from './lib/utils/common_utils';
import * as Emoji from '~/emoji'; import * as Emoji from '~/emoji';
...@@ -53,7 +52,6 @@ export const defaultAutocompleteConfig = { ...@@ -53,7 +52,6 @@ export const defaultAutocompleteConfig = {
milestones: true, milestones: true,
labels: true, labels: true,
snippets: true, snippets: true,
vulnerabilities: true,
}; };
class GfmAutoComplete { class GfmAutoComplete {
...@@ -61,7 +59,6 @@ class GfmAutoComplete { ...@@ -61,7 +59,6 @@ class GfmAutoComplete {
this.dataSources = dataSources; this.dataSources = dataSources;
this.cachedData = {}; this.cachedData = {};
this.isLoadingData = {}; this.isLoadingData = {};
this.previousQuery = '';
} }
setup(input, enableMap = defaultAutocompleteConfig) { setup(input, enableMap = defaultAutocompleteConfig) {
...@@ -557,7 +554,7 @@ class GfmAutoComplete { ...@@ -557,7 +554,7 @@ class GfmAutoComplete {
} }
getDefaultCallbacks() { getDefaultCallbacks() {
const self = this; const fetchData = this.fetchData.bind(this);
return { return {
sorter(query, items, searchKey) { sorter(query, items, searchKey) {
...@@ -570,15 +567,7 @@ class GfmAutoComplete { ...@@ -570,15 +567,7 @@ class GfmAutoComplete {
}, },
filter(query, data, searchKey) { filter(query, data, searchKey) {
if (GfmAutoComplete.isLoading(data)) { if (GfmAutoComplete.isLoading(data)) {
self.fetchData(this.$inputor, this.at); fetchData(this.$inputor, this.at);
return data;
}
if (
GfmAutoComplete.typesWithBackendFiltering.includes(GfmAutoComplete.atTypeMap[this.at]) &&
self.previousQuery !== query
) {
self.fetchData(this.$inputor, this.at, query);
self.previousQuery = query;
return data; return data;
} }
return $.fn.atwho.default.callbacks.filter(query, data, searchKey); return $.fn.atwho.default.callbacks.filter(query, data, searchKey);
...@@ -626,22 +615,13 @@ class GfmAutoComplete { ...@@ -626,22 +615,13 @@ class GfmAutoComplete {
}; };
} }
fetchData($input, at, search) { fetchData($input, at) {
if (this.isLoadingData[at]) return; if (this.isLoadingData[at]) return;
this.isLoadingData[at] = true; this.isLoadingData[at] = true;
const dataSource = this.dataSources[GfmAutoComplete.atTypeMap[at]]; const dataSource = this.dataSources[GfmAutoComplete.atTypeMap[at]];
if (GfmAutoComplete.typesWithBackendFiltering.includes(GfmAutoComplete.atTypeMap[at])) { if (this.cachedData[at]) {
axios
.get(dataSource, { params: { search } })
.then(({ data }) => {
this.loadData($input, at, data);
})
.catch(() => {
this.isLoadingData[at] = false;
});
} else if (this.cachedData[at]) {
this.loadData($input, at, this.cachedData[at]); this.loadData($input, at, this.cachedData[at]);
} else if (GfmAutoComplete.atTypeMap[at] === 'emojis') { } else if (GfmAutoComplete.atTypeMap[at] === 'emojis') {
this.loadEmojiData($input, at).catch(() => {}); this.loadEmojiData($input, at).catch(() => {});
...@@ -727,8 +707,7 @@ class GfmAutoComplete { ...@@ -727,8 +707,7 @@ class GfmAutoComplete {
// https://github.com/ichord/At.js // https://github.com/ichord/At.js
const atSymbolsWithBar = Object.keys(controllers) const atSymbolsWithBar = Object.keys(controllers)
.join('|') .join('|')
.replace(/[$]/, '\\$&') .replace(/[$]/, '\\$&');
.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, '\\$&');
...@@ -759,12 +738,9 @@ GfmAutoComplete.atTypeMap = { ...@@ -759,12 +738,9 @@ GfmAutoComplete.atTypeMap = {
'~': 'labels', '~': 'labels',
'%': 'milestones', '%': 'milestones',
'/': 'commands', '/': 'commands',
'+': 'vulnerabilities',
$: 'snippets', $: 'snippets',
}; };
GfmAutoComplete.typesWithBackendFiltering = ['vulnerabilities'];
function findEmoji(name) { function findEmoji(name) {
return Emoji.searchEmoji(name, { match: 'contains', raw: true }).sort((a, b) => { return Emoji.searchEmoji(name, { match: 'contains', raw: true }).sort((a, b) => {
if (a.index !== b.index) { if (a.index !== b.index) {
......
...@@ -16,6 +16,5 @@ export default (initGFM = true) => { ...@@ -16,6 +16,5 @@ export default (initGFM = true) => {
milestones: initGFM, milestones: initGFM,
labels: initGFM, labels: initGFM,
snippets: initGFM, snippets: initGFM,
vulnerabilities: initGFM,
}); });
}; };
...@@ -178,7 +178,6 @@ export default { ...@@ -178,7 +178,6 @@ export default {
milestones: this.enableAutocomplete, milestones: this.enableAutocomplete,
labels: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, labels: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
snippets: this.enableAutocomplete, snippets: this.enableAutocomplete,
vulnerabilities: this.enableAutocomplete,
}, },
true, true,
); );
......
...@@ -39,7 +39,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController ...@@ -39,7 +39,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController
private private
def autocomplete_service def autocomplete_service
@autocomplete_service ||= ::Projects::AutocompleteService.new(@project, current_user, params) @autocomplete_service ||= ::Projects::AutocompleteService.new(@project, current_user)
end end
def target def target
......
...@@ -159,7 +159,6 @@ module NotesHelper ...@@ -159,7 +159,6 @@ module NotesHelper
members: autocomplete, members: autocomplete,
issues: autocomplete, issues: autocomplete,
mergeRequests: autocomplete, mergeRequests: autocomplete,
vulnerabilities: autocomplete,
epics: autocomplete, epics: autocomplete,
milestones: autocomplete, milestones: autocomplete,
labels: autocomplete labels: autocomplete
......
...@@ -22,7 +22,7 @@ module Mentionable ...@@ -22,7 +22,7 @@ module Mentionable
def self.default_pattern def self.default_pattern
strong_memoize(:default_pattern) do strong_memoize(:default_pattern) do
issue_pattern = Issue.reference_pattern issue_pattern = Issue.reference_pattern
link_patterns = Regexp.union([Issue, Commit, MergeRequest, Epic, Vulnerability].map(&:link_reference_pattern).compact) link_patterns = Regexp.union([Issue, Commit, MergeRequest, Epic].map(&:link_reference_pattern).compact)
reference_pattern(link_patterns, issue_pattern) reference_pattern(link_patterns, issue_pattern)
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
# Placeholder class for model that is implemented in EE # Placeholder class for model that is implemented in EE
# It reserves '+' as a reference prefix, but the table does not exist in FOSS
class Vulnerability < ApplicationRecord class Vulnerability < ApplicationRecord
include IgnorableColumns
def self.link_reference_pattern
nil
end
def self.reference_prefix
'+'
end
def self.reference_prefix_escaped
'&plus;'
end
end end
Vulnerability.prepend_if_ee('EE::Vulnerability') Vulnerability.prepend_if_ee('EE::Vulnerability')
...@@ -425,7 +425,6 @@ GFM recognizes the following: ...@@ -425,7 +425,6 @@ GFM recognizes the following:
| merge request | `!123` | `namespace/project!123` | `project!123` | | merge request | `!123` | `namespace/project!123` | `project!123` |
| snippet | `$123` | `namespace/project$123` | `project$123` | | snippet | `$123` | `namespace/project$123` | `project$123` |
| epic **(ULTIMATE)** | `&123` | `group1/subgroup&123` | | | epic **(ULTIMATE)** | `&123` | `group1/subgroup&123` | |
| vulnerability **(ULTIMATE)** | `+123` | `namespace/project+123` | `project+123` |
| label by ID | `~123` | `namespace/project~123` | `project~123` | | label by ID | `~123` | `namespace/project~123` | `project~123` |
| one-word label by name | `~bug` | `namespace/project~bug` | `project~bug` | | one-word label by name | `~bug` | `namespace/project~bug` | `project~bug` |
| multi-word label by name | `~"feature request"` | `namespace/project~"feature request"` | `project~"feature request"` | | multi-word label by name | `~"feature request"` | `namespace/project~"feature request"` | `project~"feature request"` |
......
...@@ -16,10 +16,6 @@ class GfmAutoCompleteEE extends GfmAutoComplete { ...@@ -16,10 +16,6 @@ class GfmAutoCompleteEE extends GfmAutoComplete {
this.setupAutoCompleteEpics($input, this.getDefaultCallbacks()); this.setupAutoCompleteEpics($input, this.getDefaultCallbacks());
} }
if (this.enableMap.vulnerabilities) {
this.setupAutoCompleteVulnerabilities($input, this.getDefaultCallbacks());
}
super.setupAtWho($input); super.setupAtWho($input);
} }
...@@ -55,40 +51,6 @@ class GfmAutoCompleteEE extends GfmAutoComplete { ...@@ -55,40 +51,6 @@ class GfmAutoCompleteEE extends GfmAutoComplete {
}, },
}); });
}; };
setupAutoCompleteVulnerabilities = ($input, defaultCallbacks) => {
$input.atwho({
at: '+',
alias: 'vulnerabilities',
searchKey: 'search',
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
tmpl = GfmAutoComplete.Issues.templateFunction(value);
}
return tmpl;
},
data: GfmAutoComplete.defaultLoadingData,
insertTpl: GfmAutoComplete.Issues.insertTemplateFunction,
skipSpecialCharacterTest: true,
callbacks: {
...defaultCallbacks,
beforeSave(merges) {
return merges.map(m => {
if (m.title == null) {
return m;
}
return {
id: m.id,
title: m.title.replace(/<(?:.|\n)*?>/gm, ''),
reference: m.reference,
search: `${m.id} ${m.title}`,
};
});
},
},
});
};
} }
export default GfmAutoCompleteEE; export default GfmAutoCompleteEE;
...@@ -7,7 +7,6 @@ module EE ...@@ -7,7 +7,6 @@ module EE
prepended do prepended do
feature_category :epics, [:epics] feature_category :epics, [:epics]
feature_category :vulnerability_management, [:vulnerabilities]
end end
def epics def epics
...@@ -15,12 +14,6 @@ module EE ...@@ -15,12 +14,6 @@ module EE
render json: autocomplete_service.epics render json: autocomplete_service.epics
end end
def vulnerabilities
return render_404 unless project.feature_available?(:security_dashboard)
render json: autocomplete_service.vulnerabilities
end
end end
end end
end end
...@@ -7,7 +7,6 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController ...@@ -7,7 +7,6 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController
feature_category :issue_tracking, [:issues, :labels, :milestones, :commands] feature_category :issue_tracking, [:issues, :labels, :milestones, :commands]
feature_category :code_review, [:merge_requests] feature_category :code_review, [:merge_requests]
feature_category :epics, [:epics] feature_category :epics, [:epics]
feature_category :vulnerability_management, [:vulnerabilities]
def members def members
render json: ::Groups::ParticipantsService.new(@group, current_user).execute(target) render json: ::Groups::ParticipantsService.new(@group, current_user).execute(target)
...@@ -32,10 +31,6 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController ...@@ -32,10 +31,6 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController
render json: @autocomplete_service.epics(confidential_only: params[:confidential_only]) render json: @autocomplete_service.epics(confidential_only: params[:confidential_only])
end end
def vulnerabilities
render json: issuable_serializer.represent(@autocomplete_service.vulnerabilities, parent_group: @group)
end
def commands def commands
render json: @autocomplete_service.commands(target) render json: @autocomplete_service.commands(target)
end end
...@@ -47,7 +42,7 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController ...@@ -47,7 +42,7 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController
private private
def load_autocomplete_service def load_autocomplete_service
@autocomplete_service = ::Groups::AutocompleteService.new(@group, current_user, params) @autocomplete_service = ::Groups::AutocompleteService.new(@group, current_user)
end end
def issuable_serializer def issuable_serializer
......
...@@ -20,10 +20,10 @@ module Autocomplete ...@@ -20,10 +20,10 @@ module Autocomplete
DEFAULT_AUTOCOMPLETE_LIMIT = 5 DEFAULT_AUTOCOMPLETE_LIMIT = 5
def execute def execute
return ::Vulnerability.none unless vulnerable.feature_available?(:security_dashboard) return [] unless vulnerable.feature_available?(:security_dashboard)
::Security::VulnerabilitiesFinder # rubocop: disable CodeReuse/Finder ::Security::VulnerabilitiesFinder # rubocop: disable CodeReuse/Finder
.new(vulnerable) .new(vulnerable, params)
.execute .execute
.autocomplete_search(params[:search].to_s) .autocomplete_search(params[:search].to_s)
.with_limit(DEFAULT_AUTOCOMPLETE_LIMIT) .with_limit(DEFAULT_AUTOCOMPLETE_LIMIT)
......
...@@ -85,15 +85,13 @@ module EE ...@@ -85,15 +85,13 @@ module EE
issues: issues_group_autocomplete_sources_path(object), issues: issues_group_autocomplete_sources_path(object),
mergeRequests: merge_requests_group_autocomplete_sources_path(object), mergeRequests: merge_requests_group_autocomplete_sources_path(object),
epics: epics_group_autocomplete_sources_path(object), epics: epics_group_autocomplete_sources_path(object),
vulnerabilities: vulnerabilities_group_autocomplete_sources_path(object),
commands: commands_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), commands: commands_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
milestones: milestones_group_autocomplete_sources_path(object) milestones: milestones_group_autocomplete_sources_path(object)
} }
elsif object.group&.feature_available?(:epics)
{ epics: epics_project_autocomplete_sources_path(object) }.merge(super)
else else
{ super
epics: object.group&.feature_available?(:epics) ? epics_project_autocomplete_sources_path(object) : nil,
vulnerabilities: object.feature_available?(:security_dashboard) ? vulnerabilities_project_autocomplete_sources_path(object) : nil
}.compact.merge(super)
end end
end end
......
...@@ -11,8 +11,7 @@ module EE ...@@ -11,8 +11,7 @@ module EE
override :other_patterns override :other_patterns
def other_patterns def other_patterns
super.unshift( super.unshift(
::Epic.reference_pattern, ::Epic.reference_pattern
::Vulnerability.reference_pattern
) )
end end
end end
......
...@@ -75,7 +75,6 @@ module EE ...@@ -75,7 +75,6 @@ module EE
scope :with_created_issue_links_and_issues, -> { includes(created_issue_links: :issue) } scope :with_created_issue_links_and_issues, -> { includes(created_issue_links: :issue) }
scope :visible_to_user_and_access_level, -> (user, access_level) { where(project_id: ::Project.visible_to_user_and_access_level(user, access_level)) } scope :visible_to_user_and_access_level, -> (user, access_level) { where(project_id: ::Project.visible_to_user_and_access_level(user, access_level)) }
scope :for_ids, -> (ids) { where(id: ids) }
scope :for_projects, -> (project_ids) { where(project_id: project_ids) } scope :for_projects, -> (project_ids) { where(project_id: project_ids) }
scope :with_report_types, -> (report_types) { where(report_type: report_types) } scope :with_report_types, -> (report_types) { where(report_type: report_types) }
scope :with_severities, -> (severities) { where(severity: severities) } scope :with_severities, -> (severities) { where(severity: severities) }
...@@ -169,29 +168,6 @@ module EE ...@@ -169,29 +168,6 @@ module EE
end end
class_methods do class_methods do
def reference_pattern
@reference_pattern ||= %r{
(#{::Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}(?<vulnerability>\d+)
}x
end
def link_reference_pattern
%r{
(?<url>
#{Regexp.escape(::Gitlab.config.gitlab.url)}
\/#{::Project.reference_pattern}
(?:\/\-)
\/security\/vulnerabilities
\/(?<vulnerability>\d+)
(?<path>
(\/[a-z0-9_=-]+)*\/*
)?
(?<anchor>\#[a-z0-9_-]+)?
)
}x
end
def parent_class def parent_class
::Project ::Project
end end
......
# frozen_string_literal: true # frozen_string_literal: true
class GroupIssuableAutocompleteEntity < Grape::Entity class GroupIssuableAutocompleteEntity < Grape::Entity
expose :iid, if: -> (e, _) { e.respond_to?(:iid) } expose :iid
expose :id, if: -> (e, _) { !e.respond_to?(:iid) }
expose :title expose :title
expose :reference do |issuable, options| expose :reference do |issuable, options|
issuable.to_reference(options[:parent_group]) issuable.to_reference(options[:parent_group])
......
...@@ -7,13 +7,6 @@ module EE ...@@ -7,13 +7,6 @@ module EE
.new(current_user, group_id: project.group&.id, state: 'opened') .new(current_user, group_id: project.group&.id, state: 'opened')
.execute.select([:iid, :title]) .execute.select([:iid, :title])
end end
def vulnerabilities
::Autocomplete::VulnerabilitiesAutocompleteFinder
.new(current_user, project, params)
.execute
.select([:id, :title])
end
end end
end end
end end
...@@ -37,13 +37,6 @@ module Groups ...@@ -37,13 +37,6 @@ module Groups
.select(:iid, :title) .select(:iid, :title)
end end
def vulnerabilities
::Autocomplete::VulnerabilitiesAutocompleteFinder
.new(current_user, group, params)
.execute
.select([:id, :title, :project_id])
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def milestones def milestones
group_ids = group.self_and_ancestors.public_or_visible_to_user(current_user).pluck(:id) group_ids = group.self_and_ancestors.public_or_visible_to_user(current_user).pluck(:id)
......
---
title: Enable Special References for Vulnerabilities
merge_request: 41983
author:
type: added
...@@ -85,7 +85,6 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -85,7 +85,6 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
get 'merge_requests' get 'merge_requests'
get 'labels' get 'labels'
get 'epics' get 'epics'
get 'vulnerabilities'
get 'commands' get 'commands'
get 'milestones' get 'milestones'
end end
......
...@@ -22,7 +22,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -22,7 +22,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :autocomplete_sources, only: [] do resources :autocomplete_sources, only: [] do
collection do collection do
get 'epics' get 'epics'
get 'vulnerabilities'
end end
end end
......
# frozen_string_literal: true
module EE
module Banzai
module Filter
# HTML filter that replaces vulnerability references with links. References to
# vulnerabilities that do not exist are ignored.
#
# This filter supports cross-project/group references.
module VulnerabilityReferenceFilter
extend ActiveSupport::Concern
class_methods do
def references_in(text, pattern = object_class.reference_pattern)
text.gsub(pattern) do |match|
symbol = $~[object_sym]
if object_class.reference_valid?(symbol)
yield match, symbol.to_i, $~[:project], $~[:namespace], $~
else
match
end
end
end
end
def unescape_link(href)
return href if href =~ object_class.reference_pattern
super
end
def url_for_object(vulnerability, project)
urls = ::Gitlab::Routing.url_helpers
urls.project_security_vulnerability_url(project, vulnerability, only_path: context[:only_path])
end
def data_attributes_for(text, project, object, link_content: false, link_reference: false)
{
original: escape_html_entities(text),
link: link_content,
link_reference: link_reference,
project: project.id,
object_sym => object.id
}
end
def parent_records(parent, ids)
return Vulnerabilities.none if ids.blank? || parent.nil?
parent.vulnerabilities.for_ids(ids.to_a)
end
def record_identifier(record)
record.id.to_i
end
private
def parent_type
:project
end
end
end
end
end
...@@ -4,20 +4,15 @@ module EE ...@@ -4,20 +4,15 @@ module EE
module Banzai module Banzai
module IssuableExtractor module IssuableExtractor
EPIC_REFERENCE_TYPE = '@data-reference-type="epic"'.freeze EPIC_REFERENCE_TYPE = '@data-reference-type="epic"'.freeze
VULNERABILITY_REFERENCE_TYPE = '@data-reference-type="vulnerability"'.freeze
private private
def reference_types def reference_types
super super.push(EPIC_REFERENCE_TYPE)
.push(EPIC_REFERENCE_TYPE)
.push(VULNERABILITY_REFERENCE_TYPE)
end end
def parsers def parsers
super super.push(::Banzai::ReferenceParser::EpicParser.new(context))
.push(::Banzai::ReferenceParser::EpicParser.new(context))
.push(::Banzai::ReferenceParser::VulnerabilityParser.new(context))
end end
end end
end end
......
...@@ -24,8 +24,7 @@ module EE ...@@ -24,8 +24,7 @@ module EE
def filters def filters
[ [
*super, *super
::Banzai::Filter::VulnerabilityReferenceFilter
] ]
end end
end end
......
...@@ -11,7 +11,6 @@ module EE ...@@ -11,7 +11,6 @@ module EE
[ [
::Banzai::Filter::EpicReferenceFilter, ::Banzai::Filter::EpicReferenceFilter,
::Banzai::Filter::IterationReferenceFilter, ::Banzai::Filter::IterationReferenceFilter,
::Banzai::Filter::VulnerabilityReferenceFilter,
*super *super
] ]
end end
......
# frozen_string_literal: true
module EE
module Banzai
module ReferenceParser
module VulnerabilityParser
def references_relation
Vulnerability
end
# rubocop: disable CodeReuse/ActiveRecord
def records_for_nodes(nodes)
@vulnerabilities_for_nodes ||= grouped_objects_for_nodes(
nodes,
::Vulnerability.includes(
:author,
:project
),
self.class.data_attribute
)
end
# rubocop: enable CodeReuse/ActiveRecord
def can_read_reference?(user, vulnerability)
can?(user, :read_vulnerability, vulnerability)
end
end
end
end
end
...@@ -8,13 +8,11 @@ RSpec.describe Projects::AutocompleteSourcesController do ...@@ -8,13 +8,11 @@ RSpec.describe Projects::AutocompleteSourcesController do
let_it_be(:project) { create(:project, :public, group: group) } let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:epic) { create(:epic, group: group) } let_it_be(:epic) { create(:epic, group: group) }
let_it_be(:epic2) { create(:epic, group: group2) } let_it_be(:epic2) { create(:epic, group: group2) }
let_it_be(:vulnerability) { create(:vulnerability, project: project) }
before do before do
sign_in(user) sign_in(user)
end end
describe '#epics' do
context 'when epics feature is disabled' do context 'when epics feature is disabled' do
it 'returns 404 status' do it 'returns 404 status' do
get :epics, params: { namespace_id: project.namespace, project_id: project } get :epics, params: { namespace_id: project.namespace, project_id: project }
...@@ -41,35 +39,4 @@ RSpec.describe Projects::AutocompleteSourcesController do ...@@ -41,35 +39,4 @@ RSpec.describe Projects::AutocompleteSourcesController do
end end
end end
end end
end
describe '#vulnerabilities' do
context 'when vulnerabilities feature is disabled' do
it 'returns 404 status' do
get :vulnerabilities, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when vulnerabilities feature is enabled' do
before do
stub_licensed_features(security_dashboard: true)
project.add_developer(user)
end
describe '#vulnerabilities' do
it 'returns the correct response', :aggregate_failures do
get :vulnerabilities, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to be_an(Array)
expect(json_response.count).to eq(1)
expect(json_response.first).to include(
'id' => vulnerability.id, 'title' => vulnerability.title
)
end
end
end
end
end end
...@@ -33,31 +33,6 @@ RSpec.describe Groups::AutocompleteSourcesController do ...@@ -33,31 +33,6 @@ RSpec.describe Groups::AutocompleteSourcesController do
end end
end end
describe '#vulnerabilities' do
let_it_be_with_reload(:project) { create(:project, :private, group: group) }
let_it_be(:vulnerability) { create(:vulnerability, project: project) }
before do
project.add_developer(user)
stub_licensed_features(security_dashboard: true)
end
it 'returns 200 status' do
get :vulnerabilities, params: { group_id: group }
expect(response).to have_gitlab_http_status(:ok)
end
it 'returns the correct response', :aggregate_failures do
get :vulnerabilities, params: { group_id: group }
expect(json_response).to be_an(Array)
expect(json_response.first).to include(
'id' => vulnerability.id, 'title' => vulnerability.title
)
end
end
describe '#issues' do describe '#issues' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
......
...@@ -103,7 +103,7 @@ RSpec.describe ApplicationHelper do ...@@ -103,7 +103,7 @@ RSpec.describe ApplicationHelper do
let(:noteable_type) { Epic } let(:noteable_type) { Epic }
it 'returns paths for autocomplete_sources_controller' do it 'returns paths for autocomplete_sources_controller' do
expect_autocomplete_data_sources(object, noteable_type, [:members, :issues, :mergeRequests, :labels, :epics, :vulnerabilities, :commands, :milestones]) expect_autocomplete_data_sources(object, noteable_type, [:members, :issues, :mergeRequests, :labels, :epics, :commands, :milestones])
end end
end end
...@@ -127,23 +127,7 @@ RSpec.describe ApplicationHelper do ...@@ -127,23 +127,7 @@ RSpec.describe ApplicationHelper do
end end
end end
context 'when vulnerabilities are enabled' do context 'when epics are disabled' do
before do
stub_licensed_features(security_dashboard: true)
end
it 'returns paths for autocomplete_sources_controller for personal projects' do
expect_autocomplete_data_sources(object, noteable_type, [:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets, :vulnerabilities])
end
it 'returns paths for autocomplete_sources_controller including vulnerabilities for group projects' do
object.update_column(:namespace_id, create(:group).id)
expect_autocomplete_data_sources(object, noteable_type, [:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets, :vulnerabilities])
end
end
context 'when epics and vulnerabilities are disabled' do
it 'returns paths for autocomplete_sources_controller' do it 'returns paths for autocomplete_sources_controller' do
expect_autocomplete_data_sources(object, noteable_type, [:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets]) expect_autocomplete_data_sources(object, noteable_type, [:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets])
end end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Banzai::Filter::VulnerabilityReferenceFilter do
include FilterSpecHelper
let(:urls) { Gitlab::Routing.url_helpers }
let(:project) { create(:project) }
let(:another_project) { create(:project) }
let(:vulnerability) { create(:vulnerability, project: project) }
let(:full_ref_text) { "Check #{vulnerability.project.full_path}+#{vulnerability.id}" }
def doc(reference = nil)
reference ||= "Check +#{vulnerability.id}"
context = { project: project, group: nil }
reference_filter(reference, context)
end
context '+1 emoji' do
let(:reference) { ":+1:" }
it 'does not link to a reference' do
expect(doc(reference).css('a')).to be_empty
end
end
context 'internal reference' do
let(:reference) { "+#{vulnerability.id}" }
it 'links to a valid reference' do
expect(doc.css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(project, vulnerability))
end
it 'links with adjacent text' do
expect(doc.text).to eq("Check #{reference}")
end
it 'includes a title attribute' do
expect(doc.css('a').first.attr('title')).to eq(vulnerability.title)
end
it 'escapes the title attribute' do
vulnerability.update_column(:title, %{"></a>whatever<a title="})
expect(doc.text).to eq("Check #{reference}")
end
it 'includes default classes' do
expect(doc.css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
end
it 'includes a data-project attribute' do
link = doc.css('a').first
expect(link).to have_attribute('data-project')
expect(link.attr('data-project')).to eq(project.id.to_s)
end
it 'includes a data-vulnerability attribute' do
link = doc.css('a').first
expect(link).to have_attribute('data-vulnerability')
expect(link.attr('data-vulnerability')).to eq(vulnerability.id.to_s)
end
it 'includes a data-original attribute' do
link = doc.css('a').first
expect(link).to have_attribute('data-original')
expect(link.attr('data-original')).to eq(CGI.escapeHTML(reference))
end
it 'ignores invalid vulnerability IDs' do
text = "Check +#{non_existing_record_id}"
expect(doc(text).to_s).to eq(ERB::Util.html_escape_once(text))
end
it 'ignores out of range vulnerability IDs' do
text = "Check &1161452270761535925900804973910297"
expect(doc(text).to_s).to eq(ERB::Util.html_escape_once(text))
end
it 'does not process links containing vulnerability numbers followed by text' do
href = "#{reference}st"
link = doc("<a href='#{href}'></a>").css('a').first.attr('href')
expect(link).to eq(href)
end
end
context 'internal escaped reference' do
let(:reference) { "+us;#{vulnerability.id}" }
it 'links to a valid reference' do
expect(doc.css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(project, vulnerability))
end
it 'includes a title attribute' do
expect(doc.css('a').first.attr('title')).to eq(vulnerability.title)
end
it 'includes default classes' do
expect(doc.css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
end
it 'ignores invalid vulnerability IDs' do
text = "Check +#{non_existing_record_id}"
expect(doc(text).to_s).to eq(ERB::Util.html_escape_once(text))
end
end
context 'cross-reference' do
before do
vulnerability.update_column(:project_id, another_project.id)
end
it 'ignores a shorthand reference from another group' do
text = "Check +#{vulnerability.id}"
expect(doc(text).to_s).to eq(ERB::Util.html_escape_once(text))
end
it 'links to a valid reference for full reference' do
expect(doc(full_ref_text).css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(another_project, vulnerability))
end
it 'link has valid text' do
expect(doc(full_ref_text).css('a').first.text).to eq("#{vulnerability.project.full_path}+#{vulnerability.id}")
end
it 'includes default classes' do
expect(doc(full_ref_text).css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
end
end
context 'escaped cross-reference' do
before do
vulnerability.update_column(:project_id, another_project.id)
end
it 'ignores a shorthand reference from another group' do
text = "Check +#{vulnerability.id}"
expect(doc(text).to_s).to eq(ERB::Util.html_escape_once(text))
end
it 'links to a valid reference for full reference' do
expect(doc(full_ref_text).css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(another_project, vulnerability))
end
it 'link has valid text' do
expect(doc(full_ref_text).css('a').first.text).to eq("#{vulnerability.project.full_path}+#{vulnerability.id}")
end
it 'includes default classes' do
expect(doc(full_ref_text).css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
end
end
context 'url reference' do
let(:link) { urls.project_security_vulnerability_url(vulnerability.project, vulnerability) }
let(:text) { "Check #{link}" }
let(:project) { create(:project) }
before do
vulnerability.update_column(:project_id, another_project.id)
end
it 'links to a valid reference' do
expect(doc(text).css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(another_project, vulnerability))
end
it 'link has valid text' do
expect(doc(text).css('a').first.text).to eq(vulnerability.to_reference(project))
end
it 'includes default classes' do
expect(doc(text).css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
end
it 'matches link reference with trailing slash' do
doc2 = reference_filter("Fixed (#{link}/.)")
expect(doc2).to match(%r{\(#{Regexp.escape(vulnerability.to_reference(project))}\.\)})
end
end
context 'url in a link href' do
let(:link) { urls.project_security_vulnerability_url(vulnerability.project, vulnerability) }
let(:text) do
ref = %{<a href="#{link}">Reference</a>}
"Check #{ref}"
end
before do
vulnerability.update_column(:project_id, another_project.id)
end
it 'links to a valid reference for link href' do
expect(doc(text).css('a').first.attr('href')).to eq(urls.project_security_vulnerability_url(another_project, vulnerability))
end
it 'link has valid text' do
expect(doc(text).css('a').first.text).to eq('Reference')
end
it 'includes default classes' do
expect(doc(text).css('a').first.attr('class')).to eq('gfm gfm-vulnerability has-tooltip')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Banzai::ReferenceParser::VulnerabilityParser do
include ReferenceParserHelpers
def link(vulnerability_id)
link = empty_html_link
link['data-vulnerability'] = vulnerability_id.to_s
link
end
let(:user) { create(:user) }
let(:public_project) { create(:project, :public) }
let(:private_project1) { create(:project, :private) }
let(:private_project2) { create(:project, :private) }
let(:vulnerability) { create(:vulnerability, project: public_project) }
let(:vulnerability1) { create(:vulnerability, project: private_project1) }
let(:vulnerability2) { create(:vulnerability, project: private_project2) }
let(:nodes) do
[link(vulnerability.id), link(vulnerability1.id), link(vulnerability2.id)]
end
subject { described_class.new(Banzai::RenderContext.new(nil, user)) }
describe '#nodes_visible_to_user' do
before do
private_project1.add_developer(user)
end
context 'when the vulnerabilities feature is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
it 'returns the nodes the user can read for valid vulnerability nodes' do
expected_result = [nodes[1]]
expect(subject.nodes_visible_to_user(user, nodes)).to match_array(expected_result)
end
it 'returns an empty array for nodes without required data-attributes' do
expect(subject.nodes_visible_to_user(user, [empty_html_link])).to be_empty
end
end
context 'when the vulnerabilities feature is disabled' do
it 'returns an empty array' do
expect(subject.nodes_visible_to_user(user, nodes)).to be_empty
end
end
end
describe '#referenced_by' do
context 'when using an existing vulnerabilities IDs' do
it 'returns an Array of vulnerabilities' do
expected_result = [vulnerability, vulnerability1, vulnerability2]
expect(subject.referenced_by(nodes)).to match_array(expected_result)
end
it 'returns an empty Array for empty list of nodes' do
expect(subject.referenced_by([])).to be_empty
end
end
context 'when vulnerability with given ID does not exist' do
it 'returns an empty Array' do
expect(subject.referenced_by([link(non_existing_record_id)])).to be_empty
end
end
end
describe '#records_for_nodes' do
it 'returns a Hash containing the vulnerabilities for a list of nodes' do
expected_hash = {
nodes[0] => vulnerability,
nodes[1] => vulnerability1,
nodes[2] => vulnerability2
}
expect(subject.records_for_nodes(nodes)).to eq(expected_hash)
end
end
end
...@@ -25,18 +25,4 @@ RSpec.describe Gitlab::ReferenceExtractor do ...@@ -25,18 +25,4 @@ RSpec.describe Gitlab::ReferenceExtractor do
expect(subject.epics).to match_array([@e0, @e1]) expect(subject.epics).to match_array([@e0, @e1])
end end
it 'accesses valid vulnerabilities' do
stub_licensed_features(security_dashboard: true)
vulnerability_0 = create(:vulnerability, project: project)
vulnerability_1 = create(:vulnerability, project: project)
vulnerability_2 = create(:vulnerability, project: create(:project, :private))
text = "#{vulnerability_0.to_reference(project, full: true)}, &#{non_existing_record_iid}, #{vulnerability_1.to_reference(project, full: true)}, #{vulnerability_2.to_reference(project, full: true)}"
subject.analyze(text, { project: project })
expect(subject.vulnerabilities).to match_array([vulnerability_0, vulnerability_1])
end
end end
...@@ -157,18 +157,6 @@ RSpec.describe Vulnerability do ...@@ -157,18 +157,6 @@ RSpec.describe Vulnerability do
end end
end end
describe '.for_ids' do
let(:project) { create(:project) }
let!(:vulnerability1) { create(:vulnerability, project: project) }
let!(:vulnerability2) { create(:vulnerability, project: project) }
subject { described_class.for_ids([vulnerability1.id, vulnerability2.id]) }
it 'returns vulnerabilities with given IDs' do
is_expected.to contain_exactly(vulnerability1, vulnerability2)
end
end
describe '.for_projects' do describe '.for_projects' do
let(:project1) { create(:project) } let(:project1) { create(:project) }
let(:project2) { create(:project) } let(:project2) { create(:project) }
...@@ -504,108 +492,6 @@ RSpec.describe Vulnerability do ...@@ -504,108 +492,6 @@ RSpec.describe Vulnerability do
it { is_expected.to eq('critical' => 6, 'high' => 4, 'info' => 1, 'low' => 5, 'medium' => 2, 'unknown' => 3) } it { is_expected.to eq('critical' => 6, 'high' => 4, 'info' => 1, 'low' => 5, 'medium' => 2, 'unknown' => 3) }
end end
describe '.reference_prefix' do
subject { described_class.reference_prefix }
it { is_expected.to eq('+') }
end
describe '.reference_pattern' do
subject { described_class.reference_pattern }
it { is_expected.to match('+123') }
it { is_expected.to match('gitlab-ce+123') }
it { is_expected.to match('gitlab-org/gitlab-ce+123') }
end
describe '.link_reference_pattern' do
subject { described_class.link_reference_pattern }
it { is_expected.to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-foss/-/security/vulnerabilities/123") }
it { is_expected.not_to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-foss/security/vulnerabilities/123") }
it { is_expected.not_to match("#{Gitlab.config.gitlab.url}/gitlab-org/gitlab-foss/issues/123") }
it { is_expected.not_to match("gitlab-org/gitlab-ce/milestones/123") }
end
describe '#to_reference' do
let(:namespace) { build(:namespace, path: 'sample-namespace') }
let(:project) { build(:project, name: 'sample-project', namespace: namespace) }
let(:vulnerability) { build(:vulnerability, id: 1, project: project) }
context 'when nil argument' do
it 'returns vulnerability id' do
expect(vulnerability.to_reference).to eq '+1'
end
it 'returns complete path to the vulnerability with full: true' do
expect(vulnerability.to_reference(full: true)).to eq 'sample-namespace/sample-project+1'
end
end
context 'when argument is a project' do
context 'when same project' do
it 'returns vulnerability id' do
expect(vulnerability.to_reference(project)).to eq('+1')
end
it 'returns full reference with full: true' do
expect(vulnerability.to_reference(project, full: true)).to eq 'sample-namespace/sample-project+1'
end
end
context 'when cross-project in same namespace' do
let(:another_project) do
build(:project, name: 'another-project', namespace: project.namespace)
end
it 'returns a cross-project reference' do
expect(vulnerability.to_reference(another_project)).to eq 'sample-project+1'
end
it 'returns full reference with full: true' do
expect(vulnerability.to_reference(another_project, full: true)).to eq 'sample-namespace/sample-project+1'
end
end
context 'when cross-project in different namespace' do
let(:another_namespace) { build(:namespace, path: 'another-namespace') }
let(:another_namespace_project) { build(:project, path: 'another-project', namespace: another_namespace) }
it 'returns complete path to the vulnerability' do
expect(vulnerability.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project+1'
end
it 'returns full reference with full: true' do
expect(vulnerability.to_reference(another_namespace_project, full: true)).to eq 'sample-namespace/sample-project+1'
end
end
end
context 'when argument is a namespace' do
context 'when same as vulnerability' do
it 'returns path to the vulnerability with the project name' do
expect(vulnerability.to_reference(namespace)).to eq 'sample-project+1'
end
it 'returns full reference with full: true' do
expect(vulnerability.to_reference(namespace, full: true)).to eq 'sample-namespace/sample-project+1'
end
end
context 'when different from vulnerability namespace' do
let(:group) { build(:group, name: 'Group', path: 'sample-group') }
it 'returns full path to the vulnerability with full: true' do
expect(vulnerability.to_reference(group)).to eq 'sample-namespace/sample-project+1'
end
it 'returns full path to the vulnerability with full: false' do
expect(vulnerability.to_reference(group, full: false)).to eq 'sample-namespace/sample-project+1'
end
end
end
end
describe '#finding' do describe '#finding' do
let_it_be(:project) { create(:project, :with_vulnerability) } let_it_be(:project) { create(:project, :with_vulnerability) }
let_it_be(:vulnerability) { project.vulnerabilities.first } let_it_be(:vulnerability) { project.vulnerabilities.first }
......
...@@ -6,23 +6,12 @@ RSpec.describe GroupIssuableAutocompleteEntity do ...@@ -6,23 +6,12 @@ RSpec.describe GroupIssuableAutocompleteEntity do
let(:group) { build_stubbed(:group) } let(:group) { build_stubbed(:group) }
let(:project) { build_stubbed(:project, group: group) } let(:project) { build_stubbed(:project, group: group) }
let(:issue) { build_stubbed(:issue, project: project) } let(:issue) { build_stubbed(:issue, project: project) }
let(:vulnerability) { build_stubbed(:vulnerability, project: project) }
describe '#represent' do
context 'when issuable responds to iid' do
subject { described_class.new(issue, parent_group: group).as_json } subject { described_class.new(issue, parent_group: group).as_json }
describe '#represent' do
it 'includes the iid, title, and reference' do it 'includes the iid, title, and reference' do
expect(subject).to include(:iid, :title, :reference) expect(subject).to include(:iid, :title, :reference)
end end
end end
context 'when issuable does not respond to iid' do
subject { described_class.new(vulnerability, parent_group: project).as_json }
it 'includes the id, title, and reference' do
expect(subject).to include(:id, :title, :reference)
end
end
end
end end
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Groups::AutocompleteService do RSpec.describe Groups::AutocompleteService do
let_it_be(:group, refind: true) { create(:group, :nested, :private, avatar: fixture_file_upload('spec/fixtures/dk.png')) } let!(:group) { create(:group, :nested, :private, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
let_it_be(:sub_group) { create(:group, :private, parent: group) } let!(:sub_group) { create(:group, :private, parent: group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let!(:epic) { create(:epic, group: group, author: user) } let!(:epic) { create(:epic, group: group, author: user) }
...@@ -118,28 +118,6 @@ RSpec.describe Groups::AutocompleteService do ...@@ -118,28 +118,6 @@ RSpec.describe Groups::AutocompleteService do
end end
end end
describe '#vulnerability' do
let_it_be(:project) { create(:project, group: group) }
let_it_be(:vulnerability) { create(:vulnerability, project: project) }
before do
stub_licensed_features(security_dashboard: true)
project.add_developer(user)
end
it 'returns nothing if not allowed' do
guest = create(:user)
vulnerabilities = described_class.new(group, guest).vulnerabilities
expect(vulnerabilities).to be_empty
end
it 'returns vulnerabilities from group' do
expect(subject.vulnerabilities.map(&:id)).to contain_exactly(vulnerability.id)
end
end
describe '#commands' do describe '#commands' do
context 'when target is an epic' do context 'when target is an epic' do
let(:parent_epic) { create(:epic, group: group, author: user) } let(:parent_epic) { create(:epic, group: group, author: user) }
......
...@@ -119,7 +119,7 @@ module Banzai ...@@ -119,7 +119,7 @@ module Banzai
# Yields the link's URL and inner HTML whenever the node is a valid <a> tag. # Yields the link's URL and inner HTML whenever the node is a valid <a> tag.
def yield_valid_link(node) def yield_valid_link(node)
link = unescape_link(node.attr('href').to_s) link = CGI.unescape(node.attr('href').to_s)
inner_html = node.inner_html inner_html = node.inner_html
return unless link.force_encoding('UTF-8').valid_encoding? return unless link.force_encoding('UTF-8').valid_encoding?
...@@ -127,10 +127,6 @@ module Banzai ...@@ -127,10 +127,6 @@ module Banzai
yield link, inner_html yield link, inner_html
end end
def unescape_link(href)
CGI.unescape(href)
end
def replace_text_when_pattern_matches(node, index, pattern) def replace_text_when_pattern_matches(node, index, pattern)
return unless node.text =~ pattern return unless node.text =~ pattern
......
# frozen_string_literal: true
module Banzai
module Filter
# The actual filter is implemented in the EE mixin
class VulnerabilityReferenceFilter < IssuableReferenceFilter
self.reference_type = :vulnerability
def self.object_class
Vulnerability
end
private
def project
context[:project]
end
end
end
end
Banzai::Filter::VulnerabilityReferenceFilter.prepend_if_ee('EE::Banzai::Filter::VulnerabilityReferenceFilter')
# frozen_string_literal: true
module Banzai
module ReferenceParser
# The actual parser is implemented in the EE mixin
class VulnerabilityParser < IssuableParser
self.reference_type = :vulnerability
def records_for_nodes(_nodes)
{}
end
end
end
end
Banzai::ReferenceParser::VulnerabilityParser.prepend_if_ee('::EE::Banzai::ReferenceParser::VulnerabilityParser')
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
# Extract possible GFM references from an arbitrary String for further processing. # Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor class ReferenceExtractor < Banzai::ReferenceExtractor
REFERABLES = %i(user issue label milestone mentioned_user mentioned_group mentioned_project REFERABLES = %i(user issue label milestone mentioned_user mentioned_group mentioned_project
merge_request snippet commit commit_range directly_addressed_user epic iteration vulnerability).freeze merge_request snippet commit commit_range directly_addressed_user epic iteration).freeze
attr_accessor :project, :current_user, :author attr_accessor :project, :current_user, :author
# This counter is increased by a number of references filtered out by # This counter is increased by a number of references filtered out by
# banzai reference exctractor. Note that this counter is stateful and # banzai reference exctractor. Note that this counter is stateful and
...@@ -38,7 +38,7 @@ module Gitlab ...@@ -38,7 +38,7 @@ module Gitlab
end end
REFERABLES.each do |type| REFERABLES.each do |type|
define_method(type.to_s.pluralize) do define_method("#{type}s") do
@references[type] ||= references(type) @references[type] ||= references(type)
end end
end end
......
...@@ -8,228 +8,15 @@ import GfmAutoComplete, { membersBeforeSave } from 'ee_else_ce/gfm_auto_complete ...@@ -8,228 +8,15 @@ import GfmAutoComplete, { membersBeforeSave } from 'ee_else_ce/gfm_auto_complete
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import { getJSONFixture } from 'helpers/fixtures'; import { getJSONFixture } from 'helpers/fixtures';
import waitForPromises from 'jest/helpers/wait_for_promises';
import MockAdapter from 'axios-mock-adapter';
import AjaxCache from '~/lib/utils/ajax_cache';
import axios from '~/lib/utils/axios_utils';
const labelsFixture = getJSONFixture('autocomplete_sources/labels.json'); const labelsFixture = getJSONFixture('autocomplete_sources/labels.json');
describe('GfmAutoComplete', () => { describe('GfmAutoComplete', () => {
const fetchDataMock = { fetchData: jest.fn() }; const gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({
let gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call(fetchDataMock); fetchData: () => {},
});
let atwhoInstance; let atwhoInstance;
let sorterValue; let sorterValue;
let filterValue;
describe('.typesWithBackendFiltering', () => {
it('should contain vulnerabilities', () => {
expect(GfmAutoComplete.typesWithBackendFiltering).toContain('vulnerabilities');
});
});
describe('DefaultOptions.filter', () => {
let items;
beforeEach(() => {
jest.spyOn(fetchDataMock, 'fetchData');
jest.spyOn($.fn.atwho.default.callbacks, 'filter').mockImplementation(() => {});
});
describe('assets loading', () => {
beforeEach(() => {
atwhoInstance = { setting: {}, $inputor: 'inputor', at: '+' };
items = ['loading'];
filterValue = gfmAutoCompleteCallbacks.filter.call(atwhoInstance, '', items);
});
it('should call the fetchData function without query', () => {
expect(fetchDataMock.fetchData).toHaveBeenCalledWith('inputor', '+');
});
it('should not call the default atwho filter', () => {
expect($.fn.atwho.default.callbacks.filter).not.toHaveBeenCalled();
});
it('should return the passed unfiltered items', () => {
expect(filterValue).toEqual(items);
});
});
describe('backend filtering', () => {
beforeEach(() => {
atwhoInstance = { setting: {}, $inputor: 'inputor', at: '+' };
items = [];
});
describe('when previous query is different from current one', () => {
beforeEach(() => {
gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({
previousQuery: 'oldquery',
...fetchDataMock,
});
filterValue = gfmAutoCompleteCallbacks.filter.call(atwhoInstance, 'newquery', items);
});
it('should call the fetchData function with query', () => {
expect(fetchDataMock.fetchData).toHaveBeenCalledWith('inputor', '+', 'newquery');
});
it('should not call the default atwho filter', () => {
expect($.fn.atwho.default.callbacks.filter).not.toHaveBeenCalled();
});
it('should return the passed unfiltered items', () => {
expect(filterValue).toEqual(items);
});
});
describe('when previous query is not different from current one', () => {
beforeEach(() => {
gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({
previousQuery: 'oldquery',
...fetchDataMock,
});
filterValue = gfmAutoCompleteCallbacks.filter.call(
atwhoInstance,
'oldquery',
items,
'searchKey',
);
});
it('should not call the fetchData function', () => {
expect(fetchDataMock.fetchData).not.toHaveBeenCalled();
});
it('should call the default atwho filter', () => {
expect($.fn.atwho.default.callbacks.filter).toHaveBeenCalledWith(
'oldquery',
items,
'searchKey',
);
});
});
});
});
describe('fetchData', () => {
const { fetchData } = GfmAutoComplete.prototype;
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
jest.spyOn(axios, 'get');
jest.spyOn(AjaxCache, 'retrieve');
});
afterEach(() => {
mock.restore();
});
describe('already loading data', () => {
beforeEach(() => {
const context = {
isLoadingData: { '+': true },
dataSources: {},
cachedData: {},
};
fetchData.call(context, {}, '+', '');
});
it('should not call either axios nor AjaxCache', () => {
expect(axios.get).not.toHaveBeenCalled();
expect(AjaxCache.retrieve).not.toHaveBeenCalled();
});
});
describe('backend filtering', () => {
describe('data is not in cache', () => {
let context;
beforeEach(() => {
context = {
isLoadingData: { '+': false },
dataSources: { vulnerabilities: 'vulnerabilities_autocomplete_url' },
cachedData: {},
};
});
it('should call axios with query', () => {
fetchData.call(context, {}, '+', 'query');
expect(axios.get).toHaveBeenCalledWith('vulnerabilities_autocomplete_url', {
params: { search: 'query' },
});
});
it.each([200, 500])('should set the loading state', async responseStatus => {
mock.onGet('vulnerabilities_autocomplete_url').replyOnce(responseStatus);
fetchData.call(context, {}, '+', 'query');
expect(context.isLoadingData['+']).toBe(true);
await waitForPromises();
expect(context.isLoadingData['+']).toBe(false);
});
});
describe('data is in cache', () => {
beforeEach(() => {
const context = {
isLoadingData: { '+': false },
dataSources: { vulnerabilities: 'vulnerabilities_autocomplete_url' },
cachedData: { '+': [{}] },
};
fetchData.call(context, {}, '+', 'query');
});
it('should anyway call axios with query ignoring cache', () => {
expect(axios.get).toHaveBeenCalledWith('vulnerabilities_autocomplete_url', {
params: { search: 'query' },
});
});
});
});
describe('frontend filtering', () => {
describe('data is not in cache', () => {
beforeEach(() => {
const context = {
isLoadingData: { '#': false },
dataSources: { issues: 'issues_autocomplete_url' },
cachedData: {},
};
fetchData.call(context, {}, '#', 'query');
});
it('should call AjaxCache', () => {
expect(AjaxCache.retrieve).toHaveBeenCalledWith('issues_autocomplete_url', true);
});
});
describe('data is in cache', () => {
beforeEach(() => {
const context = {
isLoadingData: { '#': false },
dataSources: { issues: 'issues_autocomplete_url' },
cachedData: { '#': [{}] },
loadData: () => {},
};
fetchData.call(context, {}, '#', 'query');
});
it('should not call AjaxCache', () => {
expect(AjaxCache.retrieve).not.toHaveBeenCalled();
});
});
});
});
describe('DefaultOptions.sorter', () => { describe('DefaultOptions.sorter', () => {
describe('assets loading', () => { describe('assets loading', () => {
...@@ -333,7 +120,7 @@ describe('GfmAutoComplete', () => { ...@@ -333,7 +120,7 @@ describe('GfmAutoComplete', () => {
const defaultMatcher = (context, flag, subtext) => const defaultMatcher = (context, flag, subtext) =>
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);
...@@ -367,6 +154,7 @@ describe('GfmAutoComplete', () => { ...@@ -367,6 +154,7 @@ describe('GfmAutoComplete', () => {
'я', 'я',
'.', '.',
"'", "'",
'+',
'-', '-',
'_', '_',
]; ];
......
...@@ -296,7 +296,7 @@ RSpec.describe Gitlab::ReferenceExtractor do ...@@ -296,7 +296,7 @@ RSpec.describe Gitlab::ReferenceExtractor do
end end
it 'returns all supported prefixes' do it 'returns all supported prefixes' do
expect(prefixes.keys.uniq).to match_array(%w(@ # ~ % ! $ & + *iteration:)) expect(prefixes.keys.uniq).to match_array(%w(@ # ~ % ! $ & *iteration:))
end end
it 'does not allow one prefix for multiple referables if not allowed specificly' do it 'does not allow one prefix for multiple referables if not allowed specificly' do
......
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