Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-04-29 09:10:04 +00:00
parent 2f0a7423ed
commit e357d4951c
19 changed files with 350 additions and 155 deletions

View file

@ -1,10 +1,9 @@
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow */
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return */
import ListIssue from 'ee_else_ce/boards/models/issue';
import { __ } from '~/locale';
import ListLabel from './label';
import ListAssignee from './assignee';
import { urlParamsToObject } from '~/lib/utils/common_utils';
import flash from '~/flash';
import boardsStore from '../stores/boards_store';
import ListMilestone from './milestone';
@ -113,34 +112,7 @@ class List {
}
getIssues(emptyIssues = true) {
const data = {
...urlParamsToObject(boardsStore.filter.path),
page: this.page,
};
if (this.label && data.label_name) {
data.label_name = data.label_name.filter(label => label !== this.label.title);
}
if (emptyIssues) {
this.loading = true;
}
return boardsStore
.getIssuesForList(this.id, data)
.then(res => res.data)
.then(data => {
this.loading = false;
this.issuesSize = data.size;
if (emptyIssues) {
this.issues = [];
}
this.createIssues(data.issues);
return data;
});
return boardsStore.getListIssues(this, emptyIssues);
}
newIssue(issue) {

View file

@ -7,6 +7,7 @@ import Vue from 'vue';
import Cookies from 'js-cookie';
import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
import {
urlParamsToObject,
getUrlParamsArray,
parseBoolean,
convertObjectPropsToCamelCase,
@ -535,6 +536,36 @@ const boardsStore = {
});
},
getListIssues(list, emptyIssues = true) {
const data = {
...urlParamsToObject(this.filter.path),
page: list.page,
};
if (list.label && data.label_name) {
data.label_name = data.label_name.filter(label => label !== list.label.title);
}
if (emptyIssues) {
list.loading = true;
}
return this.getIssuesForList(list.id, data)
.then(res => res.data)
.then(data => {
list.loading = false;
list.issuesSize = data.size;
if (emptyIssues) {
list.issues = [];
}
list.createIssues(data.issues);
return data;
});
},
getIssuesForList(id, filter = {}) {
const data = { id };
Object.keys(filter).forEach(key => {

View file

@ -63,7 +63,7 @@ export default {
};
</script>
<template>
<div v-gl-resize-observer-directive="onResize" class="col-12 col-lg-6">
<div v-gl-resize-observer-directive="onResize">
<gl-heatmap
ref="heatmapChart"
v-bind="$attrs"

View file

@ -5,7 +5,6 @@ class SearchController < ApplicationController
include SearchHelper
include RendersCommits
before_action :override_snippet_scope, only: :show
around_action :allow_gitaly_ref_name_caching
skip_before_action :authenticate_user!
@ -104,14 +103,4 @@ class SearchController < ApplicationController
Gitlab::UsageDataCounters::SearchCounter.increment_navbar_searches_count
end
# Disallow web snippet_blobs search as we migrate snippet
# from database-backed storage to git repository-based,
# and searching across multiple git repositories is not feasible.
#
# TODO: after 13.0 refactor this into Search::SnippetService
# See https://gitlab.com/gitlab-org/gitlab/issues/208882
def override_snippet_scope
params[:scope] = 'snippet_titles' if params[:snippets] == 'true'
end
end

View file

@ -341,17 +341,6 @@ class Snippet < ApplicationRecord
fuzzy_search(query, [:title, :description, :file_name])
end
# Searches for snippets with matching content.
#
# This method uses ILIKE on PostgreSQL and LIKE on MySQL.
#
# query - The search query as a String.
#
# Returns an ActiveRecord::Relation.
def search_code(query)
fuzzy_search(query, [:content])
end
def parent_class
::Project
end

View file

@ -7,7 +7,7 @@ module Search
end
def scope
@scope ||= %w[snippet_titles].delete(params[:scope]) { 'snippet_blobs' }
@scope ||= 'snippet_titles'
end
end
end

View file

@ -0,0 +1,5 @@
---
title: Stretch heatmap metrics full column size
merge_request: 30524
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: removes store logic from issue board models
merge_request: 21400
author: nuwe1
type: other

View file

@ -38,6 +38,51 @@ type AddAwardEmojiPayload {
errors: [String!]!
}
"""
Autogenerated input type of AddProjectsToSecurityDashboard
"""
input AddProjectsToSecurityDashboardInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
IDs of projects to be added to Instance Security Dashboard
"""
projectIds: [ID!]!
}
"""
Autogenerated return type of AddProjectsToSecurityDashboard
"""
type AddProjectsToSecurityDashboardPayload {
"""
IDs of projects that were added to the Instance Security Dashboard
"""
addedProjectIds: [ID!]
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
IDs of projects that are already added to the Instance Security Dashboard
"""
duplicatedProjectIds: [ID!]
"""
Reasons why the mutation failed.
"""
errors: [String!]!
"""
IDs of projects that were not added to the Instance Security Dashboard
"""
invalidProjectIds: [ID!]
}
"""
Autogenerated input type of AdminSidekiqQueuesDeleteJobs
"""
@ -5898,6 +5943,7 @@ enum MoveType {
type Mutation {
addAwardEmoji(input: AddAwardEmojiInput!): AddAwardEmojiPayload
addProjectsToSecurityDashboard(input: AddProjectsToSecurityDashboardInput!): AddProjectsToSecurityDashboardPayload
adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload
boardListUpdateLimitMetrics(input: BoardListUpdateLimitMetricsInput!): BoardListUpdateLimitMetricsPayload
createDiffNote(input: CreateDiffNoteInput!): CreateDiffNotePayload

View file

@ -125,6 +125,168 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "AddProjectsToSecurityDashboardInput",
"description": "Autogenerated input type of AddProjectsToSecurityDashboard",
"fields": null,
"inputFields": [
{
"name": "projectIds",
"description": "IDs of projects to be added to Instance Security Dashboard",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "AddProjectsToSecurityDashboardPayload",
"description": "Autogenerated return type of AddProjectsToSecurityDashboard",
"fields": [
{
"name": "addedProjectIds",
"description": "IDs of projects that were added to the Instance Security Dashboard",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "duplicatedProjectIds",
"description": "IDs of projects that are already added to the Instance Security Dashboard",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Reasons why the mutation failed.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "invalidProjectIds",
"description": "IDs of projects that were not added to the Instance Security Dashboard",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "AdminSidekiqQueuesDeleteJobsInput",
@ -16820,6 +16982,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "addProjectsToSecurityDashboard",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "AddProjectsToSecurityDashboardInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "AddProjectsToSecurityDashboardPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "adminSidekiqQueuesDeleteJobs",
"description": null,

View file

@ -26,6 +26,18 @@ Autogenerated return type of AddAwardEmoji
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
## AddProjectsToSecurityDashboardPayload
Autogenerated return type of AddProjectsToSecurityDashboard
| Name | Type | Description |
| --- | ---- | ---------- |
| `addedProjectIds` | ID! => Array | IDs of projects that were added to the Instance Security Dashboard |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `duplicatedProjectIds` | ID! => Array | IDs of projects that are already added to the Instance Security Dashboard |
| `errors` | String! => Array | Reasons why the mutation failed. |
| `invalidProjectIds` | ID! => Array | IDs of projects that were not added to the Instance Security Dashboard |
## AdminSidekiqQueuesDeleteJobsPayload
Autogenerated return type of AdminSidekiqQueuesDeleteJobs

View file

@ -12,35 +12,17 @@ module Gitlab
end
def objects(scope, page = nil)
case scope
when 'snippet_titles'
paginated_objects(snippet_titles, page)
when 'snippet_blobs'
paginated_objects(snippet_blobs, page)
else
super(scope, nil, false)
end
paginated_objects(snippet_titles, page)
end
def formatted_count(scope)
case scope
when 'snippet_titles'
formatted_limited_count(limited_snippet_titles_count)
when 'snippet_blobs'
formatted_limited_count(limited_snippet_blobs_count)
else
super
end
formatted_limited_count(limited_snippet_titles_count)
end
def limited_snippet_titles_count
@limited_snippet_titles_count ||= limited_count(snippet_titles)
end
def limited_snippet_blobs_count
@limited_snippet_blobs_count ||= limited_count(snippet_blobs)
end
private
# rubocop: disable CodeReuse/ActiveRecord
@ -56,14 +38,6 @@ module Gitlab
snippets.search(query)
end
def snippet_blobs
snippets.search_code(query)
end
def default_scope
'snippet_blobs'
end
def paginated_objects(relation, page)
relation.page(page).per(per_page)
end

View file

@ -15337,7 +15337,7 @@ msgstr ""
msgid "Prevent users from changing their profile name"
msgstr ""
msgid "Prevent users from modifing merge request approvers list"
msgid "Prevent users from modifying merge request approvers list"
msgstr ""
msgid "Prevent users from performing write operations on GitLab while performing maintenance."

View file

@ -140,14 +140,6 @@ describe SearchController do
end
end
context 'snippet search' do
it 'forces title search' do
get :show, params: { scope: 'snippet_blobs', snippets: 'true', search: 'foo' }
expect(assigns[:scope]).to eq('snippet_titles')
end
end
it 'finds issue comments' do
project = create(:project, :public)
note = create(:note_on_issue, project: project)

View file

@ -214,6 +214,22 @@ describe('boardsStore', () => {
});
});
describe('getListIssues', () => {
let list;
beforeEach(() => {
list = new List(listObj);
setupDefaultResponses();
});
it('makes a request to get issues', () => {
const expectedResponse = expect.objectContaining({ issues: [createTestIssue()] });
expect(list.issues).toEqual([]);
return expect(boardsStore.getListIssues(list, true)).resolves.toEqual(expectedResponse);
});
});
describe('getIssuesForList', () => {
const id = 'TOO-MUCH';
const url = `${endpoints.listsEndpoint}/${id}/issues?id=${id}`;

View file

@ -5,7 +5,7 @@ require 'spec_helper'
describe Gitlab::SnippetSearchResults do
include SearchHelpers
let!(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
let_it_be(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
let(:results) { described_class.new(snippet.author, 'foo') }
describe '#snippet_titles_count' do
@ -14,27 +14,10 @@ describe Gitlab::SnippetSearchResults do
end
end
describe '#snippet_blobs_count' do
it 'returns the amount of matched snippet blobs' do
expect(results.limited_snippet_blobs_count).to eq(1)
end
end
describe '#formatted_count' do
using RSpec::Parameterized::TableSyntax
where(:scope, :count_method, :expected) do
'snippet_titles' | :limited_snippet_titles_count | max_limited_count
'snippet_blobs' | :limited_snippet_blobs_count | max_limited_count
'projects' | :limited_projects_count | max_limited_count
'unknown' | nil | nil
end
with_them do
it 'returns the expected formatted count' do
expect(results).to receive(count_method).and_return(1234) if count_method
expect(results.formatted_count(scope)).to eq(expected)
end
it 'returns the expected formatted count' do
expect(results).to receive(:limited_snippet_titles_count).and_return(1234)
expect(results.formatted_count('snippet_titles')).to eq(max_limited_count)
end
end
end

View file

@ -180,22 +180,6 @@ describe Snippet do
end
end
describe '.search_code' do
let(:snippet) { create(:snippet, content: 'class Foo; end') }
it 'returns snippets with matching content' do
expect(described_class.search_code(snippet.content)).to eq([snippet])
end
it 'returns snippets with partially matching content' do
expect(described_class.search_code('class')).to eq([snippet])
end
it 'returns snippets with matching content regardless of the casing' do
expect(described_class.search_code('FOO')).to eq([snippet])
end
end
describe 'when default snippet visibility set to internal' do
using RSpec::Parameterized::TableSyntax

View file

@ -3,59 +3,67 @@
require 'spec_helper'
describe Search::SnippetService do
let(:author) { create(:author) }
let(:project) { create(:project, :public) }
let_it_be(:author) { create(:author) }
let_it_be(:project) { create(:project, :public) }
let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') }
let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') }
let!(:private_snippet) { create(:snippet, :private, content: 'password: XXX', author: author) }
let_it_be(:public_snippet) { create(:snippet, :public, title: 'Foo Bar Title') }
let_it_be(:internal_snippet) { create(:snippet, :internal, title: 'Foo Bar Title') }
let_it_be(:private_snippet) { create(:snippet, :private, title: 'Foo Bar Title', author: author) }
let!(:project_public_snippet) { create(:snippet, :public, project: project, content: 'password: XXX') }
let!(:project_internal_snippet) { create(:snippet, :internal, project: project, content: 'password: XXX') }
let!(:project_private_snippet) { create(:snippet, :private, project: project, content: 'password: XXX') }
let_it_be(:project_public_snippet) { create(:snippet, :public, project: project, title: 'Foo Bar Title') }
let_it_be(:project_internal_snippet) { create(:snippet, :internal, project: project, title: 'Foo Bar Title') }
let_it_be(:project_private_snippet) { create(:snippet, :private, project: project, title: 'Foo Bar Title') }
let_it_be(:user) { create(:user) }
describe '#execute' do
context 'unauthenticated' do
it 'returns public snippets only' do
search = described_class.new(nil, search: 'password')
search = described_class.new(nil, search: 'bar')
results = search.execute
expect(results.objects('snippet_blobs')).to match_array [public_snippet, project_public_snippet]
expect(results.objects('snippet_titles')).to match_array [public_snippet, project_public_snippet]
end
end
context 'authenticated' do
it 'returns only public & internal snippets for regular users' do
user = create(:user)
search = described_class.new(user, search: 'password')
search = described_class.new(user, search: 'bar')
results = search.execute
expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet]
expect(results.objects('snippet_titles')).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet]
end
it 'returns public, internal snippets and project private snippets for project members' do
member = create(:user)
project.add_developer(member)
search = described_class.new(member, search: 'password')
project.add_developer(user)
search = described_class.new(user, search: 'bar')
results = search.execute
expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet, project_private_snippet]
expect(results.objects('snippet_titles')).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet, project_private_snippet]
end
it 'returns public, internal and private snippets where user is the author' do
search = described_class.new(author, search: 'password')
search = described_class.new(author, search: 'bar')
results = search.execute
expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet]
expect(results.objects('snippet_titles')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet]
end
it 'returns all snippets when user is admin' do
admin = create(:admin)
search = described_class.new(admin, search: 'password')
search = described_class.new(admin, search: 'bar')
results = search.execute
expect(results.objects('snippet_blobs')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet]
expect(results.objects('snippet_titles')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet]
end
end
end
describe '#scope' do
it 'always scopes to snippet_titles' do
search = described_class.new(user, search: 'bar')
expect(search.scope).to eq 'snippet_titles'
end
end
end

View file

@ -151,7 +151,7 @@ describe SearchService do
it 'returns the default scope' do
scope = described_class.new(user, snippets: 'true', scope: 'projects').scope
expect(scope).to eq 'snippet_blobs'
expect(scope).to eq 'snippet_titles'
end
end
@ -159,7 +159,7 @@ describe SearchService do
it 'returns the default scope' do
scope = described_class.new(user, snippets: 'true').scope
expect(scope).to eq 'snippet_blobs'
expect(scope).to eq 'snippet_titles'
end
end
end
@ -222,7 +222,7 @@ describe SearchService do
search_results = described_class.new(
user,
snippets: 'true',
search: snippet.content).search_results
search: snippet.title).search_results
expect(search_results).to be_a Gitlab::SnippetSearchResults
end
@ -270,7 +270,7 @@ describe SearchService do
search_objects = described_class.new(
user,
snippets: 'true',
search: snippet.content).search_objects
search: snippet.title).search_objects
expect(search_objects.first).to eq snippet
end
@ -383,7 +383,7 @@ describe SearchService do
let(:readable) { create(:project_snippet, project: accessible_project) }
let(:unreadable) { create(:project_snippet, project: inaccessible_project) }
let(:unredacted_results) { ar_relation(ProjectSnippet, readable, unreadable) }
let(:scope) { 'snippet_blobs' }
let(:scope) { 'snippet_titles' }
it 'redacts the inaccessible snippet' do
expect(result).to contain_exactly(readable)
@ -394,7 +394,7 @@ describe SearchService do
let(:readable) { create(:personal_snippet, :private, author: user) }
let(:unreadable) { create(:personal_snippet, :private) }
let(:unredacted_results) { ar_relation(PersonalSnippet, readable, unreadable) }
let(:scope) { 'snippet_blobs' }
let(:scope) { 'snippet_titles' }
it 'redacts the inaccessible snippet' do
expect(result).to contain_exactly(readable)