Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
22864cafe7
commit
7f5e08060f
65 changed files with 500 additions and 698 deletions
|
@ -2497,14 +2497,6 @@ Style/FrozenStringLiteralComment:
|
|||
- 'bin/secpick'
|
||||
- 'danger/changes_size/Dangerfile'
|
||||
- 'danger/metadata/Dangerfile'
|
||||
- 'qa/Gemfile'
|
||||
- 'qa/Rakefile'
|
||||
- 'qa/bin/qa'
|
||||
- 'qa/bin/rubymine'
|
||||
- 'qa/qa/fixtures/auto_devops_rack/Gemfile'
|
||||
- 'qa/qa/fixtures/auto_devops_rack/Rakefile'
|
||||
- 'qa/qa/fixtures/auto_devops_rack/config.ru'
|
||||
- 'qa/qa/page/page_concern.rb'
|
||||
- 'scripts/flaky_examples/detect-new-flaky-examples'
|
||||
- 'scripts/flaky_examples/prune-old-flaky-examples'
|
||||
- 'scripts/gather-test-memory-data'
|
||||
|
|
24
app/assets/javascripts/pages/search/show/refresh_counts.js
Normal file
24
app/assets/javascripts/pages/search/show/refresh_counts.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
function showCount(el, count) {
|
||||
el.textContent = count;
|
||||
el.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function refreshCount(el) {
|
||||
const { url } = el.dataset;
|
||||
|
||||
return axios
|
||||
.get(url)
|
||||
.then(({ data }) => showCount(el, data.count))
|
||||
.catch((e) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`Failed to fetch search count from '${url}'.`, e);
|
||||
});
|
||||
}
|
||||
|
||||
export default function refreshCounts() {
|
||||
const elements = Array.from(document.querySelectorAll('.js-search-count'));
|
||||
|
||||
return Promise.all(elements.map(refreshCount));
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result';
|
||||
import Project from '~/pages/projects/project';
|
||||
import refreshCounts from '~/pages/search/show/refresh_counts';
|
||||
import { queryToObject } from '~/lib/utils/url_utility';
|
||||
import createStore from './store';
|
||||
import { initTopbar } from './topbar';
|
||||
|
@ -19,5 +20,6 @@ export const initSearchApp = () => {
|
|||
initSearchSort(store);
|
||||
|
||||
setHighlightClass(query.search); // Code Highlighting
|
||||
refreshCounts(); // Other Scope Tab Counts
|
||||
Project.initRefSwitcher(); // Code Search Branch Picker
|
||||
};
|
||||
|
|
|
@ -1,30 +1,9 @@
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import Api from '~/api';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
/* private */
|
||||
const getCount = ({ params, state, activeCount }) => {
|
||||
const globalSearchCountsPath = '/search/count';
|
||||
const url = Api.buildUrl(globalSearchCountsPath);
|
||||
|
||||
// count is known for active tab, so return it and skip the Api call
|
||||
if (params.scope === state.query?.scope) {
|
||||
return { scope: params.scope, count: activeCount };
|
||||
}
|
||||
|
||||
return axios
|
||||
.get(url, { params })
|
||||
.then(({ data }) => {
|
||||
return { scope: params.scope, count: data.count };
|
||||
})
|
||||
.catch((e) => {
|
||||
throw e;
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchGroups = ({ commit }, search) => {
|
||||
commit(types.REQUEST_GROUPS);
|
||||
Api.groups(search)
|
||||
|
@ -59,21 +38,6 @@ export const fetchProjects = ({ commit, state }, search) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const fetchSearchCounts = ({ commit, state }, { scopeTabs, activeCount }) => {
|
||||
commit(types.REQUEST_SEARCH_COUNTS, { scopeTabs, activeCount });
|
||||
const promises = scopeTabs.map((scope) =>
|
||||
getCount({ params: { ...state.query, scope }, state, activeCount }),
|
||||
);
|
||||
|
||||
Promise.all(promises)
|
||||
.then((data) => {
|
||||
commit(types.RECEIVE_SEARCH_COUNTS_SUCCESS, data);
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash({ message: __('There was an error fetching the Search Counts') });
|
||||
});
|
||||
};
|
||||
|
||||
export const setQuery = ({ commit }, { key, value }) => {
|
||||
commit(types.SET_QUERY, { key, value });
|
||||
};
|
||||
|
@ -82,22 +46,6 @@ export const applyQuery = ({ state }) => {
|
|||
visitUrl(setUrlParams({ ...state.query, page: null }));
|
||||
};
|
||||
|
||||
export const resetQuery = ({ state }, snippets = false) => {
|
||||
let defaultQuery = {
|
||||
page: null,
|
||||
state: null,
|
||||
confidential: null,
|
||||
nav_source: null,
|
||||
};
|
||||
|
||||
if (snippets) {
|
||||
defaultQuery = {
|
||||
snippets: true,
|
||||
group_id: null,
|
||||
project_id: null,
|
||||
...defaultQuery,
|
||||
};
|
||||
}
|
||||
|
||||
visitUrl(setUrlParams({ ...state.query, ...defaultQuery }));
|
||||
export const resetQuery = ({ state }) => {
|
||||
visitUrl(setUrlParams({ ...state.query, page: null, state: null, confidential: null }));
|
||||
};
|
||||
|
|
|
@ -6,7 +6,4 @@ export const REQUEST_PROJECTS = 'REQUEST_PROJECTS';
|
|||
export const RECEIVE_PROJECTS_SUCCESS = 'RECEIVE_PROJECTS_SUCCESS';
|
||||
export const RECEIVE_PROJECTS_ERROR = 'RECEIVE_PROJECTS_ERROR';
|
||||
|
||||
export const REQUEST_SEARCH_COUNTS = 'REQUEST_SEARCH_COUNTS';
|
||||
export const RECEIVE_SEARCH_COUNTS_SUCCESS = 'RECEIVE_SEARCH_COUNTS_SUCCESS';
|
||||
|
||||
export const SET_QUERY = 'SET_QUERY';
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { ALL_SCOPE_TABS } from '~/search/topbar/constants';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
export default {
|
||||
|
@ -24,16 +23,6 @@ export default {
|
|||
state.fetchingProjects = false;
|
||||
state.projects = [];
|
||||
},
|
||||
[types.REQUEST_SEARCH_COUNTS](state, { scopeTabs, activeCount }) {
|
||||
state.inflatedScopeTabs = scopeTabs.map((tab) => {
|
||||
return { ...ALL_SCOPE_TABS[tab], count: tab === state.query?.scope ? activeCount : '' };
|
||||
});
|
||||
},
|
||||
[types.RECEIVE_SEARCH_COUNTS_SUCCESS](state, data) {
|
||||
state.inflatedScopeTabs = data.map((tab) => {
|
||||
return { ...ALL_SCOPE_TABS[tab.scope], count: tab.count };
|
||||
});
|
||||
},
|
||||
[types.SET_QUERY](state, { key, value }) {
|
||||
state.query[key] = value;
|
||||
},
|
||||
|
|
|
@ -4,6 +4,5 @@ const createState = ({ query }) => ({
|
|||
fetchingGroups: false,
|
||||
projects: [],
|
||||
fetchingProjects: false,
|
||||
inflatedScopeTabs: [],
|
||||
});
|
||||
export default createState;
|
||||
|
|
|
@ -3,7 +3,6 @@ import { mapState, mapActions } from 'vuex';
|
|||
import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui';
|
||||
import GroupFilter from './group_filter.vue';
|
||||
import ProjectFilter from './project_filter.vue';
|
||||
import ScopeTabs from './scope_tabs.vue';
|
||||
|
||||
export default {
|
||||
name: 'GlobalSearchTopbar',
|
||||
|
@ -13,7 +12,6 @@ export default {
|
|||
GroupFilter,
|
||||
ProjectFilter,
|
||||
GlButton,
|
||||
ScopeTabs,
|
||||
},
|
||||
props: {
|
||||
groupInitialData: {
|
||||
|
@ -26,16 +24,6 @@ export default {
|
|||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
scopeTabs: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
count: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['query']),
|
||||
|
@ -50,9 +38,6 @@ export default {
|
|||
showFilters() {
|
||||
return !this.query.snippets || this.query.snippets === 'false';
|
||||
},
|
||||
showScopeTabs() {
|
||||
return this.query.search;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['applyQuery', 'setQuery']),
|
||||
|
@ -61,31 +46,28 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<gl-form class="search-page-form" @submit.prevent="applyQuery">
|
||||
<section class="gl-lg-display-flex gl-align-items-flex-end">
|
||||
<div class="gl-flex-fill-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2">
|
||||
<label>{{ __('What are you searching for?') }}</label>
|
||||
<gl-search-box-by-type
|
||||
id="dashboard_search"
|
||||
v-model="search"
|
||||
name="search"
|
||||
:placeholder="__(`Search for projects, issues, etc.`)"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
|
||||
<label class="gl-display-block">{{ __('Group') }}</label>
|
||||
<group-filter :initial-data="groupInitialData" />
|
||||
</div>
|
||||
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
|
||||
<label class="gl-display-block">{{ __('Project') }}</label>
|
||||
<project-filter :initial-data="projectInitialData" />
|
||||
</div>
|
||||
<gl-button class="btn-search gl-lg-ml-2" variant="success" type="submit">{{
|
||||
__('Search')
|
||||
}}</gl-button>
|
||||
</section>
|
||||
</gl-form>
|
||||
<scope-tabs v-if="showScopeTabs" :scope-tabs="scopeTabs" :count="count" />
|
||||
</section>
|
||||
<gl-form class="search-page-form" @submit.prevent="applyQuery">
|
||||
<section class="gl-lg-display-flex gl-align-items-flex-end">
|
||||
<div class="gl-flex-fill-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2">
|
||||
<label>{{ __('What are you searching for?') }}</label>
|
||||
<gl-search-box-by-type
|
||||
id="dashboard_search"
|
||||
v-model="search"
|
||||
name="search"
|
||||
:placeholder="__(`Search for projects, issues, etc.`)"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
|
||||
<label class="gl-display-block">{{ __('Group') }}</label>
|
||||
<group-filter :initial-data="groupInitialData" />
|
||||
</div>
|
||||
<div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-2">
|
||||
<label class="gl-display-block">{{ __('Project') }}</label>
|
||||
<project-filter :initial-data="projectInitialData" />
|
||||
</div>
|
||||
<gl-button class="btn-search gl-lg-ml-2" variant="success" type="submit">{{
|
||||
__('Search')
|
||||
}}</gl-button>
|
||||
</section>
|
||||
</gl-form>
|
||||
</template>
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
<script>
|
||||
import { GlTabs, GlTab, GlBadge } from '@gitlab/ui';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'ScopeTabs',
|
||||
components: {
|
||||
GlTabs,
|
||||
GlTab,
|
||||
GlBadge,
|
||||
},
|
||||
props: {
|
||||
scopeTabs: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
count: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['query', 'inflatedScopeTabs']),
|
||||
},
|
||||
created() {
|
||||
this.fetchSearchCounts({ scopeTabs: this.scopeTabs, activeCount: this.count });
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchSearchCounts', 'setQuery', 'resetQuery']),
|
||||
handleTabChange(scope) {
|
||||
this.setQuery({ key: 'scope', value: scope });
|
||||
this.resetQuery(scope === 'snippet_titles');
|
||||
},
|
||||
isTabActive(scope) {
|
||||
return scope === this.query.scope;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<gl-tabs
|
||||
content-class="gl-p-0"
|
||||
nav-class="search-filter search-nav-tabs gl-display-flex gl-overflow-x-auto"
|
||||
>
|
||||
<gl-tab
|
||||
v-for="tab in inflatedScopeTabs"
|
||||
:key="tab.scope"
|
||||
class="gl-display-flex"
|
||||
:active="isTabActive(tab.scope)"
|
||||
:data-testid="`tab-${tab.scope}`"
|
||||
:title-link-attributes="{ 'data-qa-selector': tab.qaSelector }"
|
||||
title-link-class="gl-white-space-nowrap"
|
||||
@click="handleTabChange(tab.scope)"
|
||||
>
|
||||
<template #title>
|
||||
<span data-testid="tab-title"> {{ tab.title }} </span>
|
||||
<gl-badge
|
||||
v-show="tab.count"
|
||||
:data-scope="tab.scope"
|
||||
:data-testid="`badge-${tab.scope}`"
|
||||
:variant="isTabActive(tab.scope) ? 'neutral' : 'muted'"
|
||||
size="sm"
|
||||
>
|
||||
{{ tab.count }}
|
||||
</gl-badge>
|
||||
</template>
|
||||
</gl-tab>
|
||||
</gl-tabs>
|
||||
</div>
|
||||
</template>
|
|
@ -19,17 +19,3 @@ export const PROJECT_DATA = {
|
|||
selectedDisplayValue: 'name_with_namespace',
|
||||
itemsDisplayValue: 'name_with_namespace',
|
||||
};
|
||||
|
||||
export const ALL_SCOPE_TABS = {
|
||||
blobs: { scope: 'blobs', title: __('Code'), qaSelector: 'code_tab' },
|
||||
issues: { scope: 'issues', title: __('Issues') },
|
||||
merge_requests: { scope: 'merge_requests', title: __('Merge requests') },
|
||||
milestones: { scope: 'milestones', title: __('Milestones') },
|
||||
notes: { scope: 'notes', title: __('Comments') },
|
||||
wiki_blobs: { scope: 'wiki_blobs', title: __('Wiki') },
|
||||
commits: { scope: 'commits', title: __('Commits') },
|
||||
epics: { scope: 'epics', title: __('Epics') },
|
||||
users: { scope: 'users', title: __('Users') },
|
||||
snippet_titles: { scope: 'snippet_titles', title: __('Titles and Descriptions') },
|
||||
projects: { scope: 'projects', title: __('Projects'), qaSelector: 'projects_tab' },
|
||||
};
|
||||
|
|
|
@ -11,12 +11,10 @@ export const initTopbar = (store) => {
|
|||
return false;
|
||||
}
|
||||
|
||||
let { groupInitialData, projectInitialData, scopeTabs } = el.dataset;
|
||||
const { count } = el.dataset;
|
||||
let { groupInitialData, projectInitialData } = el.dataset;
|
||||
|
||||
groupInitialData = JSON.parse(groupInitialData);
|
||||
projectInitialData = JSON.parse(projectInitialData);
|
||||
scopeTabs = JSON.parse(scopeTabs);
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
|
@ -26,8 +24,6 @@ export const initTopbar = (store) => {
|
|||
props: {
|
||||
groupInitialData,
|
||||
projectInitialData,
|
||||
scopeTabs,
|
||||
count,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -2,7 +2,6 @@ $search-dropdown-max-height: 400px;
|
|||
$search-avatar-size: 16px;
|
||||
$search-sidebar-min-width: 240px;
|
||||
$search-sidebar-max-width: 300px;
|
||||
$search-topbar-min-height: 111px;
|
||||
|
||||
.search-results {
|
||||
.search-result-row {
|
||||
|
@ -20,12 +19,6 @@ $search-topbar-min-height: 111px;
|
|||
}
|
||||
}
|
||||
|
||||
.search-topbar {
|
||||
@include media-breakpoint-up(md) {
|
||||
min-height: $search-topbar-min-height;
|
||||
}
|
||||
}
|
||||
|
||||
.search-sidebar {
|
||||
@include media-breakpoint-up(md) {
|
||||
min-width: $search-sidebar-min-width;
|
||||
|
@ -33,11 +26,6 @@ $search-topbar-min-height: 111px;
|
|||
}
|
||||
}
|
||||
|
||||
.search-nav-tabs {
|
||||
overflow-y: hidden;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.search form:hover,
|
||||
.file-finder-input:hover,
|
||||
.issuable-search-form:hover,
|
||||
|
|
|
@ -34,16 +34,26 @@ module BoardsActions
|
|||
|
||||
def boards
|
||||
strong_memoize(:boards) do
|
||||
Boards::ListService.new(parent, current_user).execute
|
||||
existing_boards = boards_finder.execute
|
||||
if existing_boards.any?
|
||||
existing_boards
|
||||
else
|
||||
# if no board exists, create one
|
||||
[board_create_service.execute.payload]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def board
|
||||
strong_memoize(:board) do
|
||||
boards.find(params[:id])
|
||||
board_finder.execute.first
|
||||
end
|
||||
end
|
||||
|
||||
def board_type
|
||||
board_klass.to_type
|
||||
end
|
||||
|
||||
def serializer
|
||||
BoardSerializer.new(current_user: current_user)
|
||||
end
|
||||
|
|
|
@ -65,6 +65,7 @@ module MultipleBoardsActions
|
|||
private
|
||||
|
||||
def redirect_to_recent_board
|
||||
return unless board_type == Board.to_type
|
||||
return if request.format.json? || !parent.multiple_issue_boards_available? || !latest_visited_board
|
||||
|
||||
redirect_to board_path(latest_visited_board.board)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class Groups::BoardsController < Groups::ApplicationController
|
||||
include BoardsActions
|
||||
include RecordUserLastActivity
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
before_action :authorize_read_board!, only: [:index, :show]
|
||||
before_action :assign_endpoint_vars
|
||||
|
@ -14,6 +15,28 @@ class Groups::BoardsController < Groups::ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def board_klass
|
||||
Board
|
||||
end
|
||||
|
||||
def boards_finder
|
||||
strong_memoize :boards_finder do
|
||||
Boards::ListService.new(parent, current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def board_finder
|
||||
strong_memoize :board_finder do
|
||||
Boards::ListService.new(parent, current_user, board_id: params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
def board_create_service
|
||||
strong_memoize :board_create_service do
|
||||
Boards::CreateService.new(parent, current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def assign_endpoint_vars
|
||||
@boards_endpoint = group_boards_path(group)
|
||||
@namespace_path = group.to_param
|
||||
|
|
|
@ -15,6 +15,28 @@ class Projects::BoardsController < Projects::ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def board_klass
|
||||
Board
|
||||
end
|
||||
|
||||
def boards_finder
|
||||
strong_memoize :boards_finder do
|
||||
Boards::ListService.new(parent, current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def board_finder
|
||||
strong_memoize :board_finder do
|
||||
Boards::ListService.new(parent, current_user, board_id: params[:id])
|
||||
end
|
||||
end
|
||||
|
||||
def board_create_service
|
||||
strong_memoize :board_create_service do
|
||||
Boards::CreateService.new(parent, current_user)
|
||||
end
|
||||
end
|
||||
|
||||
def assign_endpoint_vars
|
||||
@boards_endpoint = project_boards_path(project)
|
||||
@bulk_issues_path = bulk_update_project_issues_path(project)
|
||||
|
|
|
@ -13,7 +13,7 @@ module Resolvers
|
|||
def resolve(id: nil)
|
||||
return unless parent
|
||||
|
||||
::Boards::ListService.new(parent, context[:current_user], board_id: extract_board_id(id)).execute(create_default_board: false).first
|
||||
::Boards::ListService.new(parent, context[:current_user], board_id: extract_board_id(id)).execute.first
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ module Resolvers
|
|||
|
||||
return Board.none unless parent
|
||||
|
||||
::Boards::ListService.new(parent, context[:current_user], board_id: extract_board_id(id)).execute(create_default_board: false)
|
||||
::Boards::ListService.new(parent, context[:current_user], board_id: extract_board_id(id)).execute
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
Board.none
|
||||
end
|
||||
|
|
|
@ -21,7 +21,8 @@ module BoardsHelper
|
|||
group_id: @group&.id,
|
||||
labels_filter_base_path: build_issue_link_base,
|
||||
labels_fetch_path: labels_fetch_path,
|
||||
labels_manage_path: labels_manage_path
|
||||
labels_manage_path: labels_manage_path,
|
||||
board_type: board.to_type
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -511,8 +511,7 @@ module ProjectsHelper
|
|||
commits: :download_code,
|
||||
merge_requests: :read_merge_request,
|
||||
notes: [:read_merge_request, :download_code, :read_issue, :read_snippet],
|
||||
members: :read_project_member,
|
||||
wiki_blobs: :read_wiki
|
||||
members: :read_project_member
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SearchHelper
|
||||
PROJECT_SEARCH_TABS = %i{blobs issues merge_requests milestones notes wiki_blobs commits}.freeze
|
||||
BASIC_SEARCH_TABS = %i{projects issues merge_requests milestones}.freeze
|
||||
SEARCH_GENERIC_PARAMS = [
|
||||
:search,
|
||||
:scope,
|
||||
:project_id,
|
||||
:group_id,
|
||||
:repository_ref,
|
||||
:snippets,
|
||||
:sort,
|
||||
:force_search_results
|
||||
].freeze
|
||||
|
||||
def search_autocomplete_opts(term)
|
||||
return unless current_user
|
||||
|
@ -284,19 +292,27 @@ module SearchHelper
|
|||
Sanitize.clean(str)
|
||||
end
|
||||
|
||||
def search_nav_tabs
|
||||
return [:snippet_titles] if !@project && @show_snippets
|
||||
def search_filter_link(scope, label, data: {}, search: {})
|
||||
search_params = params
|
||||
.merge(search)
|
||||
.merge({ scope: scope })
|
||||
.permit(SEARCH_GENERIC_PARAMS)
|
||||
|
||||
tabs =
|
||||
if @project
|
||||
PROJECT_SEARCH_TABS.select { |tab| project_search_tabs?(tab) }
|
||||
else
|
||||
BASIC_SEARCH_TABS.dup
|
||||
if @scope == scope
|
||||
li_class = 'active'
|
||||
count = @search_results.formatted_count(scope)
|
||||
else
|
||||
badge_class = 'js-search-count hidden'
|
||||
badge_data = { url: search_count_path(search_params) }
|
||||
end
|
||||
|
||||
content_tag :li, class: li_class, data: data do
|
||||
link_to search_path(search_params) do
|
||||
concat label
|
||||
concat ' '
|
||||
concat content_tag(:span, count, class: ['badge badge-pill', badge_class], data: badge_data)
|
||||
end
|
||||
|
||||
tabs << :users if show_user_search_tab?
|
||||
|
||||
tabs
|
||||
end
|
||||
end
|
||||
|
||||
def search_filter_input_options(type, placeholder = _('Search or filter results...'))
|
||||
|
|
|
@ -44,6 +44,14 @@ class Board < ApplicationRecord
|
|||
def scoped?
|
||||
false
|
||||
end
|
||||
|
||||
def self.to_type
|
||||
name.demodulize
|
||||
end
|
||||
|
||||
def to_type
|
||||
self.class.to_type
|
||||
end
|
||||
end
|
||||
|
||||
Board.prepend_if_ee('EE::Board')
|
||||
|
|
|
@ -11,6 +11,9 @@ module Ci
|
|||
{ unknown_failure: 'Unknown pipeline failure!',
|
||||
config_error: 'CI/CD YAML configuration error!',
|
||||
external_validation_failure: 'External pipeline validation failed!',
|
||||
activity_limit_exceeded: 'Pipeline activity limit exceeded!',
|
||||
size_limit_exceeded: 'Pipeline size limit exceeded!',
|
||||
job_activity_limit_exceeded: 'Pipeline job activity limit exceeded!',
|
||||
deployments_limit_exceeded: 'Pipeline deployments limit exceeded!' }
|
||||
end
|
||||
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
module Boards
|
||||
class ListService < Boards::BaseService
|
||||
def execute(create_default_board: true)
|
||||
create_board! if create_default_board && parent.boards.empty?
|
||||
|
||||
def execute
|
||||
find_boards
|
||||
end
|
||||
|
||||
|
@ -18,10 +16,6 @@ module Boards
|
|||
parent.boards.first_board
|
||||
end
|
||||
|
||||
def create_board!
|
||||
Boards::CreateService.new(parent, current_user).execute
|
||||
end
|
||||
|
||||
def find_boards
|
||||
found =
|
||||
if parent.multiple_issue_boards_available?
|
||||
|
|
|
@ -5,6 +5,7 @@ module Boards
|
|||
class CreateService < Boards::BaseService
|
||||
def execute(board)
|
||||
return unless current_user && Gitlab::Database.read_write?
|
||||
return unless board.is_a?(Board) # other board types do not support board visits yet
|
||||
|
||||
if parent.is_a?(Group)
|
||||
BoardGroupRecentVisit.visited!(current_user, board)
|
||||
|
|
35
app/views/search/_category.html.haml
Normal file
35
app/views/search/_category.html.haml
Normal file
|
@ -0,0 +1,35 @@
|
|||
- users = capture_haml do
|
||||
- if show_user_search_tab?
|
||||
= search_filter_link 'users', _("Users")
|
||||
|
||||
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
|
||||
.fade-left= sprite_icon('chevron-lg-left', size: 12)
|
||||
.fade-right= sprite_icon('chevron-lg-right', size: 12)
|
||||
%ul.nav-links.search-filter.scrolling-tabs.nav.nav-tabs
|
||||
- if @project
|
||||
- if project_search_tabs?(:blobs)
|
||||
= search_filter_link 'blobs', _("Code"), data: { qa_selector: 'code_tab' }
|
||||
- if project_search_tabs?(:issues)
|
||||
= search_filter_link 'issues', _("Issues")
|
||||
- if project_search_tabs?(:merge_requests)
|
||||
= search_filter_link 'merge_requests', _("Merge requests")
|
||||
- if project_search_tabs?(:milestones)
|
||||
= search_filter_link 'milestones', _("Milestones")
|
||||
- if project_search_tabs?(:notes)
|
||||
= search_filter_link 'notes', _("Comments")
|
||||
- if project_search_tabs?(:wiki)
|
||||
= search_filter_link 'wiki_blobs', _("Wiki")
|
||||
- if project_search_tabs?(:commits)
|
||||
= search_filter_link 'commits', _("Commits")
|
||||
= users
|
||||
|
||||
- elsif @show_snippets
|
||||
= search_filter_link 'snippet_titles', _("Titles and Descriptions"), search: { snippets: true, group_id: nil, project_id: nil }
|
||||
- else
|
||||
= search_filter_link 'projects', _("Projects"), data: { qa_selector: 'projects_tab' }
|
||||
= search_filter_link 'issues', _("Issues")
|
||||
= search_filter_link 'merge_requests', _("Merge requests")
|
||||
= search_filter_link 'milestones', _("Milestones")
|
||||
= render_if_exists 'search/epics_filter_link'
|
||||
= render_if_exists 'search/category_elasticsearch'
|
||||
= users
|
|
@ -16,6 +16,7 @@
|
|||
= render_if_exists 'search/form_elasticsearch', attrs: { class: 'mb-2 mb-sm-0 align-self-center' }
|
||||
|
||||
.gl-mt-3
|
||||
#js-search-topbar.search-topbar{ data: { "group-initial-data": @group.to_json, "project-initial-data": project_attributes.to_json, "scope-tabs": search_nav_tabs.to_json, count: @search_results&.formatted_count(@scope) } }
|
||||
#js-search-topbar{ data: { "group-initial-data": @group.to_json, "project-initial-data": project_attributes.to_json } }
|
||||
- if @search_term
|
||||
= render 'search/category'
|
||||
= render 'search/results'
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
- @no_breadcrumb_container = true
|
||||
- @no_container = true
|
||||
- @content_class = "issue-boards-content js-focus-mode-board"
|
||||
- breadcrumb_title _("Issue Boards")
|
||||
- if board.to_type == "EpicBoard"
|
||||
- breadcrumb_title _("Epic Boards")
|
||||
- else
|
||||
- breadcrumb_title _("Issue Boards")
|
||||
- page_title("#{board.name}", _("Boards"))
|
||||
- add_page_specific_style 'page_bundles/boards'
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Change search tab to Vue component
|
||||
merge_request: 52018
|
||||
author:
|
||||
type: changed
|
5
changelogs/unreleased/jimcser-openapi-readme.yml
Normal file
5
changelogs/unreleased/jimcser-openapi-readme.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Adds README to OpenAPI docs
|
||||
merge_request: 52637
|
||||
author: Jim Cser @jimcser
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Speed up update merge request worker by memoizing whether a push is a force or not
|
||||
merge_request: 53536
|
||||
author:
|
||||
type: performance
|
BIN
doc/api/openapi/img/apiviewer01-fs8.png
Normal file
BIN
doc/api/openapi/img/apiviewer01-fs8.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
BIN
doc/api/openapi/img/apiviewer03-fs8.png
Normal file
BIN
doc/api/openapi/img/apiviewer03-fs8.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
doc/api/openapi/img/apiviewer04-fs8.png
Normal file
BIN
doc/api/openapi/img/apiviewer04-fs8.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
42
doc/api/openapi/openapi_interactive.md
Normal file
42
doc/api/openapi/openapi_interactive.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Interactive API documentation
|
||||
|
||||
Introduces the interactive documentation tool for the GitLab API.
|
||||
|
||||
## About the OpenAPI specification
|
||||
|
||||
The [OpenAPI specification](https://swagger.io/specification/) (formerly called Swagger) defines a standard, language-agnostic interface to RESTful APIs. OpenAPI definition files are written in the YAML format, which is automatically rendered by the GitLab browser into a more human-readable interface. For general information about the GitLab APIs, see [API Docs](../README.md).
|
||||
|
||||
## Overview
|
||||
|
||||
The [interactive API documentation tool](openapi.yaml) allows API testing directly on the GitLab.com
|
||||
website. Only a few of the available endpoints are documented with the OpenAPI spec, but the current
|
||||
list demonstrates the functionality of the tool.
|
||||
|
||||
![API viewer screenshot](img/apiviewer01-fs8.png)
|
||||
|
||||
## Endpoint parameters
|
||||
|
||||
When you expand an endpoint listing, you'll see a description, input parameters (if required),
|
||||
and example server responses. Some parameters include a default or a list of allowed values.
|
||||
|
||||
![API viewer screenshot](img/apiviewer04-fs8.png)
|
||||
|
||||
## Starting an interactive sesssion
|
||||
|
||||
A [Personal access token](../../user/profile/personal_access_tokens.md) (PAT) is one way to
|
||||
start an interactive session. To do this, select **Authorize** from the main page, and a
|
||||
dialog box prompts you to enter your PAT, which is valid for the current web session.
|
||||
|
||||
To test the endpoint, first select **Try it out** on the endpoint definition page. Input the parameters
|
||||
as required, then select **Execute**. In the following example, we executed a request for the `version`
|
||||
endpoint (no parameters required). The tool shows the `curl` command and URL of the request, followed
|
||||
by the server responses that are returned. You can create new responses by editing the relevant parameters
|
||||
and then select **Execute** once again.
|
||||
|
||||
![API viewer screenshot](img/apiviewer03-fs8.png)
|
|
@ -233,46 +233,47 @@ Table description links:
|
|||
- [Source](../install/installation.md)
|
||||
- [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit)
|
||||
|
||||
| Component | Description | Omnibus GitLab | GitLab chart | Minikube Minimal | GitLab.com | Source | GDK | CE/EE |
|
||||
|-------------------------------------------------------|----------------------------------------------------------------------|:--------------:|:------------:|:----------------:|:----------:|:------:|:---:|:-------:|
|
||||
| [Certificate Management](#certificate-management) | TLS Settings, Let's Encrypt | ✅ | ✅ | ⚙ | ✅ | ⚙ | ⚙ | CE & EE |
|
||||
| [Consul](#consul) | Database node discovery, failover | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only |
|
||||
| [Database Migrations](#database-migrations) | Database migrations | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [Elasticsearch](#elasticsearch) | Improved search within GitLab | ⤓ | ⤓ | ⤓ | ✅ | ⤓ | ⤓ | EE Only |
|
||||
| [Gitaly](#gitaly) | Git RPC service for handling all Git calls made by GitLab | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [GitLab Exporter](#gitlab-exporter) | Generates a variety of GitLab metrics | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [GitLab Geo Node](#gitlab-geo) | Geographically distributed GitLab nodes | ⚙ | ⚙ | ❌ | ✅ | ❌ | ⚙ | EE Only |
|
||||
| [GitLab Managed Apps](#gitlab-managed-apps) | Deploy Helm, Ingress, Cert-Manager, Prometheus, GitLab Runner, JupyterHub, or Knative to a cluster | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | CE & EE |
|
||||
| [GitLab Pages](#gitlab-pages) | Hosts static websites | ⚙ | ❌ | ❌ | ✅ | ⚙ | ⚙ | CE & EE |
|
||||
| [GitLab Kubernetes Agent](#gitlab-kubernetes-agent) | Integrate Kubernetes clusters in a cloud-native way | ⚙ | ⚙ | ❌ | ❌ | ⤓ | ⚙ | EE Only |
|
||||
| [GitLab self-monitoring: Alertmanager](#alertmanager) | Deduplicates, groups, and routes alerts from Prometheus | ⚙ | ✅ | ⚙ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [GitLab self-monitoring: Grafana](#grafana) | Metrics dashboard | ✅ | ⚙ | ⤓ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [GitLab self-monitoring: Jaeger](#jaeger) | View traces generated by the GitLab instance | ❌ | ⚙ | ❌ | ❌ | ⤓ | ⚙ | CE & EE |
|
||||
| [GitLab self-monitoring: Prometheus](#prometheus) | Time-series database, metrics collection, and query service | ✅ | ✅ | ⚙ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [GitLab self-monitoring: Sentry](#sentry) | Track errors generated by the GitLab instance | ⤓ | ⤓ | ❌ | ✅ | ⤓ | ⤓ | CE & EE |
|
||||
| [GitLab Shell](#gitlab-shell) | Handles `git` over SSH sessions | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [GitLab Workhorse](#gitlab-workhorse) | Smart reverse proxy, handles large HTTP requests | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [Inbound email (SMTP)](#inbound-email) | Receive messages to update issues | ⤓ | ⚙ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE |
|
||||
| [Jaeger integration](#jaeger) | Distributed tracing for deployed apps | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | EE Only |
|
||||
| [LDAP Authentication](#ldap-authentication) | Authenticate users against centralized LDAP directory | ⤓ | ⤓ | ⤓ | ❌ | ⤓ | ⤓ | CE & EE |
|
||||
| [Mattermost](#mattermost) | Open-source Slack alternative | ⚙ | ⤓ | ⤓ | ⤓ | ❌ | ❌ | CE & EE |
|
||||
| [MinIO](#minio) | Object storage service | ⤓ | ✅ | ✅ | ✅ | ❌ | ⚙ | CE & EE |
|
||||
| [NGINX](#nginx) | Routes requests to appropriate components, terminates SSL | ✅ | ✅ | ⚙ | ✅ | ⤓ | ❌ | CE & EE |
|
||||
| [Node Exporter](#node-exporter) | Prometheus endpoint with system metrics | ✅ | N/A | N/A | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [Outbound email (SMTP)](#outbound-email) | Send email messages to users | ⤓ | ⚙ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE |
|
||||
| [Patroni](#patroni) | Manage PostgreSQL HA cluster leader selection and replication | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only |
|
||||
| [PgBouncer Exporter](#pgbouncer-exporter) | Prometheus endpoint with PgBouncer metrics | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [PgBouncer](#pgbouncer) | Database connection pooling, failover | ⚙ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only |
|
||||
| [PostgreSQL Exporter](#postgresql-exporter) | Prometheus endpoint with PostgreSQL metrics | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [PostgreSQL](#postgresql) | Database | ✅ | ✅ | ✅ | ✅ | ⤓ | ✅ | CE & EE |
|
||||
| [Praefect](#praefect) | A transparent proxy between any Git client and Gitaly storage nodes. | ✅ | ⚙ | ❌ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [Redis Exporter](#redis-exporter) | Prometheus endpoint with Redis metrics | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [Redis](#redis) | Caching service | ✅ | ✅ | ✅ | ✅ | ⤓ | ✅ | CE & EE |
|
||||
| [Registry](#registry) | Container registry, allows pushing and pulling of images | ⚙ | ✅ | ✅ | ✅ | ⤓ | ⚙ | CE & EE |
|
||||
| [Runner](#gitlab-runner) | Executes GitLab CI/CD jobs | ⤓ | ✅ | ⚙ | ✅ | ⚙ | ⚙ | CE & EE |
|
||||
| [Sentry integration](#sentry) | Error tracking for deployed apps | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | CE & EE |
|
||||
| [Sidekiq](#sidekiq) | Background jobs processor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | CE & EE |
|
||||
| [Puma (GitLab Rails)](#puma) | Handles requests for the web interface and API | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| Component | Description | Omnibus GitLab | GitLab Environment Toolkit (GET) - Omnibus | GitLab chart | Minikube Minimal | GitLab.com | Source | GDK | CE/EE |
|
||||
|-------------------------------------------------------|----------------------------------------------------------------------|:--------------:|:--------------:|:------------:|:----------------:|:----------:|:------:|:---:|:-------:|
|
||||
| [Certificate Management](#certificate-management) | TLS Settings, Let's Encrypt | ✅ | ✅ | ✅ | ⚙ | ✅ | ⚙ | ⚙ | CE & EE |
|
||||
| [Consul](#consul) | Database node discovery, failover | ⚙ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only |
|
||||
| [Database Migrations](#database-migrations) | Database migrations | ✅ | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [Elasticsearch](#elasticsearch) | Improved search within GitLab | ⤓ | ⚙ | ⤓ | ⤓ | ✅ | ⤓ | ⤓ | EE Only |
|
||||
| [Gitaly](#gitaly) | Git RPC service for handling all Git calls made by GitLab | ✅ | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [GitLab Exporter](#gitlab-exporter) | Generates a variety of GitLab metrics | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [GitLab Geo Node](#gitlab-geo) | Geographically distributed GitLab nodes | ⚙ | ⚙ | ❌ | ❌ | ✅ | ❌ | ⚙ | EE Only |
|
||||
| [GitLab Kubernetes Agent](#gitlab-kubernetes-agent) | Integrate Kubernetes clusters in a cloud-native way | ⚙ | ⚙ | ⚙ | ❌ | ❌ | ⤓ | ⚙ | EE Only |
|
||||
| [GitLab Managed Apps](#gitlab-managed-apps) | Deploy Helm, Ingress, Cert-Manager, Prometheus, GitLab Runner, JupyterHub, or Knative to a cluster | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | CE & EE |
|
||||
| [GitLab Pages](#gitlab-pages) | Hosts static websites | ⚙ | ⚙ | ❌ | ❌ | ✅ | ⚙ | ⚙ | CE & EE |
|
||||
| [GitLab Kubernetes Agent](#gitlab-kubernetes-agent) | Integrate Kubernetes clusters in a cloud-native way | ⚙ | ⚙ | ⚙ | ❌ | ❌ | ⤓ | ⚙ | EE Only |
|
||||
| [GitLab self-monitoring: Alertmanager](#alertmanager) | Deduplicates, groups, and routes alerts from Prometheus | ⚙ | ⚙ | ✅ | ⚙ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [GitLab self-monitoring: Grafana](#grafana) | Metrics dashboard | ✅ | ✅ | ⚙ | ⤓ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [GitLab self-monitoring: Jaeger](#jaeger) | View traces generated by the GitLab instance | ❌ | ⚙ | ⚙ | ❌ | ❌ | ⤓ | ⚙ | CE & EE |
|
||||
| [GitLab self-monitoring: Prometheus](#prometheus) | Time-series database, metrics collection, and query service | ✅ | ✅ | ✅ | ⚙ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [GitLab self-monitoring: Sentry](#sentry) | Track errors generated by the GitLab instance | ⤓ | ⤓ | ⤓ | ❌ | ✅ | ⤓ | ⤓ | CE & EE |
|
||||
| [GitLab Shell](#gitlab-shell) | Handles `git` over SSH sessions | ✅ | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [GitLab Workhorse](#gitlab-workhorse) | Smart reverse proxy, handles large HTTP requests | ✅ | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [Inbound email (SMTP)](#inbound-email) | Receive messages to update issues | ⤓ | ⤓ | ⚙ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE |
|
||||
| [Jaeger integration](#jaeger) | Distributed tracing for deployed apps | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | EE Only |
|
||||
| [LDAP Authentication](#ldap-authentication) | Authenticate users against centralized LDAP directory | ⤓ | ⤓ | ⤓ | ⤓ | ❌ | ⤓ | ⤓ | CE & EE |
|
||||
| [Mattermost](#mattermost) | Open-source Slack alternative | ⚙ | ⚙ | ⤓ | ⤓ | ⤓ | ❌ | ❌ | CE & EE |
|
||||
| [MinIO](#minio) | Object storage service | ⤓ | ⤓ | ✅ | ✅ | ✅ | ❌ | ⚙ | CE & EE |
|
||||
| [NGINX](#nginx) | Routes requests to appropriate components, terminates SSL | ✅ | ✅ | ✅ | ⚙ | ✅ | ⤓ | ❌ | CE & EE |
|
||||
| [Node Exporter](#node-exporter) | Prometheus endpoint with system metrics | ✅ | ✅ | N/A | N/A | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [Outbound email (SMTP)](#outbound-email) | Send email messages to users | ⤓ | ⤓ | ⚙ | ⤓ | ✅ | ⤓ | ⤓ | CE & EE |
|
||||
| [Patroni](#patroni) | Manage PostgreSQL HA cluster leader selection and replication | ⚙ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only |
|
||||
| [PgBouncer Exporter](#pgbouncer-exporter) | Prometheus endpoint with PgBouncer metrics | ⚙ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [PgBouncer](#pgbouncer) | Database connection pooling, failover | ⚙ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | EE Only |
|
||||
| [PostgreSQL Exporter](#postgresql-exporter) | Prometheus endpoint with PostgreSQL metrics | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [PostgreSQL](#postgresql) | Database | ✅ | ✅ | ✅ | ✅ | ✅ | ⤓ | ✅ | CE & EE |
|
||||
| [Praefect](#praefect) | A transparent proxy between any Git client and Gitaly storage nodes. | ✅ | ✅ | ⚙ | ❌ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [Puma (GitLab Rails)](#puma) | Handles requests for the web interface and API | ✅ | ✅ | ✅ | ✅ | ✅ | ⚙ | ✅ | CE & EE |
|
||||
| [Redis Exporter](#redis-exporter) | Prometheus endpoint with Redis metrics | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | CE & EE |
|
||||
| [Redis](#redis) | Caching service | ✅ | ✅ | ✅ | ✅ | ✅ | ⤓ | ✅ | CE & EE |
|
||||
| [Registry](#registry) | Container registry, allows pushing and pulling of images | ⚙ | ⚙ | ✅ | ✅ | ✅ | ⤓ | ⚙ | CE & EE |
|
||||
| [Runner](#gitlab-runner) | Executes GitLab CI/CD jobs | ⤓ | ⤓ | ✅ | ⚙ | ✅ | ⚙ | ⚙ | CE & EE |
|
||||
| [Sentry integration](#sentry) | Error tracking for deployed apps | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | ⤓ | CE & EE |
|
||||
| [Sidekiq](#sidekiq) | Background jobs processor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | CE & EE |
|
||||
|
||||
### Component details
|
||||
|
||||
|
|
|
@ -33,7 +33,9 @@ module Gitlab
|
|||
end
|
||||
|
||||
def force_push?
|
||||
Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
|
||||
strong_memoize(:force_push) do
|
||||
Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
|
||||
end
|
||||
end
|
||||
|
||||
def branch_push?
|
||||
|
|
|
@ -11523,6 +11523,9 @@ msgstr ""
|
|||
msgid "Epic"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epic Boards"
|
||||
msgstr ""
|
||||
|
||||
msgid "Epic cannot be found."
|
||||
msgstr ""
|
||||
|
||||
|
@ -29563,9 +29566,6 @@ msgstr ""
|
|||
msgid "There was an error fetching the Node's Groups"
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error fetching the Search Counts"
|
||||
msgstr ""
|
||||
|
||||
msgid "There was an error fetching the deploy freezes."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
"autosize": "^4.0.2",
|
||||
"aws-sdk": "^2.637.0",
|
||||
"axios": "^0.20.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"bootstrap": "4.4.1",
|
||||
"brace-expansion": "^1.1.8",
|
||||
|
@ -69,7 +69,7 @@
|
|||
"codemirror": "^5.48.4",
|
||||
"codesandbox-api": "0.0.23",
|
||||
"compression-webpack-plugin": "^5.0.2",
|
||||
"copy-webpack-plugin": "^5.0.5",
|
||||
"copy-webpack-plugin": "^5.1.2",
|
||||
"core-js": "^3.8.3",
|
||||
"cron-validator": "^1.1.1",
|
||||
"cropper": "^2.3.0",
|
||||
|
@ -125,7 +125,7 @@
|
|||
"prosemirror-markdown": "^1.3.0",
|
||||
"prosemirror-model": "^1.6.4",
|
||||
"raphael": "^2.2.7",
|
||||
"raw-loader": "^4.0.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"select2": "3.5.2-browserify",
|
||||
"smooshpack": "^0.0.62",
|
||||
"sortablejs": "^1.10.2",
|
||||
|
@ -154,7 +154,7 @@
|
|||
"vuex": "^3.6.0",
|
||||
"web-vitals": "^0.2.4",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-bundle-analyzer": "^3.9.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-stats-plugin": "^0.3.1",
|
||||
"worker-loader": "^2.0.0",
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'gitlab-qa'
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'qa/tools/revoke_all_personal_access_tokens'
|
||||
require_relative 'qa/tools/delete_subgroups'
|
||||
require_relative 'qa/tools/generate_perf_testdata'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../qa'
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../qa'
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
gem 'rack'
|
||||
gem 'rake'
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rake/testtask'
|
||||
|
||||
task default: %w[test]
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
run lambda { |env| [200, { 'Content-Type' => 'text/plain' }, StringIO.new("Hello World! #{ENV['OPTIONAL_MESSAGE']}\n")] }
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
module Page
|
||||
module PageConcern
|
||||
|
|
|
@ -4,7 +4,7 @@ module QA
|
|||
module Page
|
||||
module Search
|
||||
class Results < QA::Page::Base
|
||||
view 'app/assets/javascripts/search/topbar/constants.js' do
|
||||
view 'app/views/search/_category.html.haml' do
|
||||
element :code_tab
|
||||
element :projects_tab
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ RSpec.describe 'Global search' do
|
|||
create_list(:issue, 2, project: project, title: 'initial')
|
||||
end
|
||||
|
||||
it "has a pagination", :js do
|
||||
it "has a pagination" do
|
||||
submit_search('initial')
|
||||
select_search_scope('Issues')
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User searches for code', :js do
|
||||
RSpec.describe 'User searches for code' do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :repository, namespace: user.namespace) }
|
||||
|
||||
|
@ -16,7 +16,6 @@ RSpec.describe 'User searches for code', :js do
|
|||
visit(project_path(project))
|
||||
|
||||
submit_search('application.js')
|
||||
|
||||
select_search_scope('Code')
|
||||
|
||||
expect(page).to have_selector('.results', text: 'application.js')
|
||||
|
@ -25,7 +24,7 @@ RSpec.describe 'User searches for code', :js do
|
|||
expect(page).to have_link('application.js', href: /master\/files\/js\/application.js/)
|
||||
end
|
||||
|
||||
context 'when on a project page' do
|
||||
context 'when on a project page', :js do
|
||||
before do
|
||||
visit(search_path)
|
||||
find('[data-testid="project-filter"]').click
|
||||
|
@ -49,7 +48,7 @@ RSpec.describe 'User searches for code', :js do
|
|||
expect(current_url).to match(/master\/.gitignore#L3/)
|
||||
end
|
||||
|
||||
it 'search multiple words with refs switching' do
|
||||
it 'search mutiple words with refs switching' do
|
||||
expected_result = 'Use `snake_case` for naming files'
|
||||
search = 'for naming files'
|
||||
|
||||
|
@ -68,7 +67,7 @@ RSpec.describe 'User searches for code', :js do
|
|||
end
|
||||
end
|
||||
|
||||
context 'search code within refs' do
|
||||
context 'search code within refs', :js do
|
||||
let(:ref_name) { 'v1.0.0' }
|
||||
|
||||
before do
|
||||
|
@ -86,9 +85,9 @@ RSpec.describe 'User searches for code', :js do
|
|||
expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
|
||||
end
|
||||
|
||||
# this example is use to test the design that the refs is not
|
||||
# only represent the branch as well as the tags.
|
||||
it 'ref switcher list all the branches and tags' do
|
||||
# this example is use to test the desgine that the refs is not
|
||||
# only repersent the branch as well as the tags.
|
||||
it 'ref swither list all the branchs and tags' do
|
||||
find('.js-project-refs-dropdown').click
|
||||
expect(find('.dropdown-page-one .dropdown-content')).to have_link('sha-starting-with-large-number')
|
||||
expect(find('.dropdown-page-one .dropdown-content')).to have_link('v1.0.0')
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User searches for comments', :js do
|
||||
RSpec.describe 'User searches for comments' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'User searches for users', :js do
|
||||
RSpec.describe 'User searches for users' do
|
||||
let(:user1) { create(:user, username: 'gob_bluth', name: 'Gob Bluth') }
|
||||
let(:user2) { create(:user, username: 'michael_bluth', name: 'Michael Bluth') }
|
||||
let(:user3) { create(:user, username: 'gob_2018', name: 'George Oscar Bluth') }
|
||||
|
@ -12,7 +12,7 @@ RSpec.describe 'User searches for users', :js do
|
|||
end
|
||||
|
||||
context 'when on the dashboard' do
|
||||
it 'finds the user' do
|
||||
it 'finds the user', :js do
|
||||
visit dashboard_projects_path
|
||||
|
||||
submit_search('gob')
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Search Snippets', :js do
|
||||
RSpec.describe 'Search Snippets' do
|
||||
it 'user searches for snippets by title' do
|
||||
public_snippet = create(:personal_snippet, :public, title: 'Beginning and Middle')
|
||||
private_snippet = create(:personal_snippet, :private, title: 'Middle and End')
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`pages/search/show/refresh_counts fetches and displays search counts 1`] = `
|
||||
"<div class=\\"badge\\">22</div>
|
||||
<div class=\\"badge js-search-count\\" data-url=\\"http://test.host/search/count?search=lorem+ipsum&project_id=3&scope=issues\\">4</div>
|
||||
<div class=\\"badge js-search-count\\" data-url=\\"http://test.host/search/count?search=lorem+ipsum&project_id=3&scope=merge_requests\\">5</div>"
|
||||
`;
|
38
spec/frontend/pages/search/show/refresh_counts_spec.js
Normal file
38
spec/frontend/pages/search/show/refresh_counts_spec.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import { TEST_HOST } from 'helpers/test_constants';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import refreshCounts from '~/pages/search/show/refresh_counts';
|
||||
|
||||
const URL = `${TEST_HOST}/search/count?search=lorem+ipsum&project_id=3`;
|
||||
const urlWithScope = (scope) => `${URL}&scope=${scope}`;
|
||||
const counts = [
|
||||
{ scope: 'issues', count: 4 },
|
||||
{ scope: 'merge_requests', count: 5 },
|
||||
];
|
||||
const fixture = `<div class="badge">22</div>
|
||||
<div class="badge js-search-count hidden" data-url="${urlWithScope('issues')}"></div>
|
||||
<div class="badge js-search-count hidden" data-url="${urlWithScope('merge_requests')}"></div>`;
|
||||
|
||||
describe('pages/search/show/refresh_counts', () => {
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
setFixtures(fixture);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('fetches and displays search counts', () => {
|
||||
counts.forEach(({ scope, count }) => {
|
||||
mock.onGet(urlWithScope(scope)).reply(200, { count });
|
||||
});
|
||||
|
||||
// assert before act behavior
|
||||
return refreshCounts().then(() => {
|
||||
expect(document.body.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -61,28 +61,3 @@ export const MOCK_SORT_OPTIONS = [
|
|||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const MOCK_SEARCH_COUNTS_INPUT = {
|
||||
scopeTabs: ['issues', 'snippet_titles', 'merge_requests'],
|
||||
activeCount: '15',
|
||||
};
|
||||
|
||||
export const MOCK_SEARCH_COUNT = { scope: 'issues', count: '15' };
|
||||
|
||||
export const MOCK_SEARCH_COUNTS_SUCCESS = [
|
||||
{ scope: 'issues', count: '15' },
|
||||
{ scope: 'snippet_titles', count: '15' },
|
||||
{ scope: 'merge_requests', count: '15' },
|
||||
];
|
||||
|
||||
export const MOCK_SEARCH_COUNTS = [
|
||||
{ scope: 'issues', count: '15' },
|
||||
{ scope: 'snippet_titles', count: '5' },
|
||||
{ scope: 'merge_requests', count: '1' },
|
||||
];
|
||||
|
||||
export const MOCK_SCOPE_TABS = [
|
||||
{ scope: 'issues', title: 'Issues', count: '15' },
|
||||
{ scope: 'snippet_titles', title: 'Titles and Descriptions', count: '5' },
|
||||
{ scope: 'merge_requests', title: 'Merge requests', count: '1' },
|
||||
];
|
||||
|
|
|
@ -7,15 +7,7 @@ import * as urlUtils from '~/lib/utils/url_utility';
|
|||
import createState from '~/search/store/state';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import createFlash from '~/flash';
|
||||
import {
|
||||
MOCK_QUERY,
|
||||
MOCK_GROUPS,
|
||||
MOCK_PROJECT,
|
||||
MOCK_PROJECTS,
|
||||
MOCK_SEARCH_COUNT,
|
||||
MOCK_SEARCH_COUNTS_SUCCESS,
|
||||
MOCK_SEARCH_COUNTS_INPUT,
|
||||
} from '../mock_data';
|
||||
import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECT, MOCK_PROJECTS } from '../mock_data';
|
||||
|
||||
jest.mock('~/flash');
|
||||
jest.mock('~/lib/utils/url_utility', () => ({
|
||||
|
@ -45,21 +37,19 @@ describe('Global Search Store Actions', () => {
|
|||
});
|
||||
|
||||
describe.each`
|
||||
action | axiosMock | payload | type | expectedMutations | callback
|
||||
${actions.fetchGroups} | ${{ method: 'onGet', code: 200, res: MOCK_GROUPS }} | ${null} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${noCallback}
|
||||
${actions.fetchGroups} | ${{ method: 'onGet', code: 500, res: null }} | ${null} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${flashCallback}
|
||||
${actions.fetchProjects} | ${{ method: 'onGet', code: 200, res: MOCK_PROJECTS }} | ${null} | ${'success'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_SUCCESS, payload: MOCK_PROJECTS }]} | ${noCallback}
|
||||
${actions.fetchProjects} | ${{ method: 'onGet', code: 500, res: null }} | ${null} | ${'error'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_ERROR }]} | ${flashCallback}
|
||||
${actions.fetchSearchCounts} | ${{ method: 'onGet', code: 200, res: MOCK_SEARCH_COUNT }} | ${MOCK_SEARCH_COUNTS_INPUT} | ${'success'} | ${[{ type: types.REQUEST_SEARCH_COUNTS, payload: MOCK_SEARCH_COUNTS_INPUT }, { type: types.RECEIVE_SEARCH_COUNTS_SUCCESS, payload: MOCK_SEARCH_COUNTS_SUCCESS }]} | ${noCallback}
|
||||
${actions.fetchSearchCounts} | ${{ method: 'onGet', code: 500, res: null }} | ${MOCK_SEARCH_COUNTS_INPUT} | ${'error'} | ${[{ type: types.REQUEST_SEARCH_COUNTS, payload: MOCK_SEARCH_COUNTS_INPUT }]} | ${flashCallback}
|
||||
`(`axios calls`, ({ action, axiosMock, payload, type, expectedMutations, callback }) => {
|
||||
action | axiosMock | type | expectedMutations | callback
|
||||
${actions.fetchGroups} | ${{ method: 'onGet', code: 200, res: MOCK_GROUPS }} | ${'success'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_SUCCESS, payload: MOCK_GROUPS }]} | ${noCallback}
|
||||
${actions.fetchGroups} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_GROUPS }, { type: types.RECEIVE_GROUPS_ERROR }]} | ${flashCallback}
|
||||
${actions.fetchProjects} | ${{ method: 'onGet', code: 200, res: MOCK_PROJECTS }} | ${'success'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_SUCCESS, payload: MOCK_PROJECTS }]} | ${noCallback}
|
||||
${actions.fetchProjects} | ${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_PROJECTS }, { type: types.RECEIVE_PROJECTS_ERROR }]} | ${flashCallback}
|
||||
`(`axios calls`, ({ action, axiosMock, type, expectedMutations, callback }) => {
|
||||
describe(action.name, () => {
|
||||
describe(`on ${type}`, () => {
|
||||
beforeEach(() => {
|
||||
mock[axiosMock.method]().reply(axiosMock.code, axiosMock.res);
|
||||
mock[axiosMock.method]().replyOnce(axiosMock.code, axiosMock.res);
|
||||
});
|
||||
it(`should dispatch the correct mutations`, () => {
|
||||
return testAction({ action, payload, state, expectedMutations }).then(() => callback());
|
||||
return testAction({ action, state, expectedMutations }).then(() => callback());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -125,25 +115,9 @@ describe('Global Search Store Actions', () => {
|
|||
page: null,
|
||||
state: null,
|
||||
confidential: null,
|
||||
nav_source: null,
|
||||
});
|
||||
expect(urlUtils.visitUrl).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('calls setUrlParams with snippets, group_id, and project_id when snippets param is true', () => {
|
||||
return testAction(actions.resetQuery, true, state, [], [], () => {
|
||||
expect(urlUtils.setUrlParams).toHaveBeenCalledWith({
|
||||
...state.query,
|
||||
page: null,
|
||||
state: null,
|
||||
confidential: null,
|
||||
nav_source: null,
|
||||
group_id: null,
|
||||
project_id: null,
|
||||
snippets: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
import mutations from '~/search/store/mutations';
|
||||
import createState from '~/search/store/state';
|
||||
import * as types from '~/search/store/mutation_types';
|
||||
import {
|
||||
MOCK_QUERY,
|
||||
MOCK_GROUPS,
|
||||
MOCK_PROJECTS,
|
||||
MOCK_SEARCH_COUNTS,
|
||||
MOCK_SCOPE_TABS,
|
||||
} from '../mock_data';
|
||||
import { MOCK_QUERY, MOCK_GROUPS, MOCK_PROJECTS } from '../mock_data';
|
||||
|
||||
describe('Global Search Store Mutations', () => {
|
||||
let state;
|
||||
|
@ -77,32 +71,4 @@ describe('Global Search Store Mutations', () => {
|
|||
expect(state.query[payload.key]).toBe(payload.value);
|
||||
});
|
||||
});
|
||||
|
||||
describe('REQUEST_SEARCH_COUNTS', () => {
|
||||
it('sets the count to for the query.scope activeCount', () => {
|
||||
const payload = { scopeTabs: ['issues'], activeCount: '22' };
|
||||
mutations[types.REQUEST_SEARCH_COUNTS](state, payload);
|
||||
|
||||
expect(state.inflatedScopeTabs).toStrictEqual([
|
||||
{ scope: 'issues', title: 'Issues', count: '22' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('sets other scopes count to empty string', () => {
|
||||
const payload = { scopeTabs: ['milestones'], activeCount: '22' };
|
||||
mutations[types.REQUEST_SEARCH_COUNTS](state, payload);
|
||||
|
||||
expect(state.inflatedScopeTabs).toStrictEqual([
|
||||
{ scope: 'milestones', title: 'Milestones', count: '' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('RECEIVE_SEARCH_COUNTS_SUCCESS', () => {
|
||||
it('sets the count from the input for all tabs', () => {
|
||||
mutations[types.RECEIVE_SEARCH_COUNTS_SUCCESS](state, MOCK_SEARCH_COUNTS);
|
||||
|
||||
expect(state.inflatedScopeTabs).toStrictEqual(MOCK_SCOPE_TABS);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,6 @@ import { MOCK_QUERY } from 'jest/search/mock_data';
|
|||
import GlobalSearchTopbar from '~/search/topbar/components/app.vue';
|
||||
import GroupFilter from '~/search/topbar/components/group_filter.vue';
|
||||
import ProjectFilter from '~/search/topbar/components/project_filter.vue';
|
||||
import ScopeTabs from '~/search/topbar/components/scope_tabs.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
@ -43,7 +42,6 @@ describe('GlobalSearchTopbar', () => {
|
|||
const findGroupFilter = () => wrapper.find(GroupFilter);
|
||||
const findProjectFilter = () => wrapper.find(ProjectFilter);
|
||||
const findSearchButton = () => wrapper.find(GlButton);
|
||||
const findScopeTabs = () => wrapper.find(ScopeTabs);
|
||||
|
||||
describe('template', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -54,18 +52,6 @@ describe('GlobalSearchTopbar', () => {
|
|||
expect(findTopbarForm().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('Scope Tabs', () => {
|
||||
it('renders when search param is set', () => {
|
||||
createComponent({ query: { search: 'test' } });
|
||||
expect(findScopeTabs().exists()).toBe(true);
|
||||
});
|
||||
it('does not render search param is blank', () => {
|
||||
createComponent({ query: {} });
|
||||
|
||||
expect(findScopeTabs().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Search box', () => {
|
||||
it('renders always', () => {
|
||||
expect(findGlSearchBox().exists()).toBe(true);
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
import Vuex from 'vuex';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import { GlTabs, GlTab, GlBadge } from '@gitlab/ui';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import { MOCK_QUERY, MOCK_SCOPE_TABS } from 'jest/search/mock_data';
|
||||
import ScopeTabs from '~/search/topbar/components/scope_tabs.vue';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe('ScopeTabs', () => {
|
||||
let wrapper;
|
||||
|
||||
const actionSpies = {
|
||||
fetchSearchCounts: jest.fn(),
|
||||
setQuery: jest.fn(),
|
||||
resetQuery: jest.fn(),
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
scopeTabs: ['issues', 'merge_requests', 'milestones'],
|
||||
count: '20',
|
||||
};
|
||||
|
||||
const createComponent = (props = {}, initialState = {}) => {
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
query: {
|
||||
...MOCK_QUERY,
|
||||
search: 'test',
|
||||
},
|
||||
...initialState,
|
||||
},
|
||||
actions: actionSpies,
|
||||
});
|
||||
|
||||
wrapper = extendedWrapper(
|
||||
mount(ScopeTabs, {
|
||||
localVue,
|
||||
store,
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const findScopeTabs = () => wrapper.find(GlTabs);
|
||||
const findTabs = () => wrapper.findAll(GlTab);
|
||||
const findBadges = () => wrapper.findAll(GlBadge);
|
||||
const findTabsTitle = () =>
|
||||
wrapper.findAll('[data-testid="tab-title"]').wrappers.map((w) => w.text());
|
||||
const findBadgesTitle = () => findBadges().wrappers.map((w) => w.text());
|
||||
const findBadgeByScope = (scope) => wrapper.findByTestId(`badge-${scope}`);
|
||||
const findTabByScope = (scope) => wrapper.findByTestId(`tab-${scope}`);
|
||||
|
||||
describe('template', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({}, { inflatedScopeTabs: MOCK_SCOPE_TABS });
|
||||
});
|
||||
|
||||
it('always renders Scope Tabs', () => {
|
||||
expect(findScopeTabs().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('findTabs', () => {
|
||||
it('renders a tab for each scope', () => {
|
||||
expect(findTabs()).toHaveLength(defaultProps.scopeTabs.length);
|
||||
expect(findTabsTitle()).toStrictEqual([
|
||||
'Issues',
|
||||
'Titles and Descriptions',
|
||||
'Merge requests',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findBadges', () => {
|
||||
it('renders a badge for each scope', () => {
|
||||
expect(findBadges()).toHaveLength(defaultProps.scopeTabs.length);
|
||||
expect(findBadgesTitle()).toStrictEqual(['15', '5', '1']);
|
||||
});
|
||||
|
||||
it('sets the variant to neutral for active tab only', () => {
|
||||
expect(findBadgeByScope('issues').classes()).toContain('badge-neutral');
|
||||
expect(findBadgeByScope('snippet_titles').classes()).toContain('badge-muted');
|
||||
expect(findBadgeByScope('merge_requests').classes()).toContain('badge-muted');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({}, { inflatedScopeTabs: MOCK_SCOPE_TABS });
|
||||
|
||||
findTabByScope('snippet_titles').vm.$emit('click');
|
||||
});
|
||||
|
||||
describe('handleTabChange', () => {
|
||||
it('calls setQuery with scope, applies any search params from ALL_SCOPE_TABS, and sends nulls for page, state, confidential, and nav_source', () => {
|
||||
expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), {
|
||||
key: 'scope',
|
||||
value: 'snippet_titles',
|
||||
});
|
||||
});
|
||||
|
||||
it('calls resetQuery and sends true for snippet_titles tab', () => {
|
||||
expect(actionSpies.resetQuery).toHaveBeenCalledWith(expect.any(Object), true);
|
||||
});
|
||||
|
||||
it('calls resetQuery and does not send true for other tabs', () => {
|
||||
findTabByScope('issues').vm.$emit('click');
|
||||
expect(actionSpies.resetQuery).toHaveBeenCalledWith(expect.any(Object), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -392,6 +392,63 @@ RSpec.describe SearchHelper do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'search_filter_link' do
|
||||
it 'renders a search filter link for the current scope' do
|
||||
@scope = 'projects'
|
||||
@search_results = double
|
||||
|
||||
expect(@search_results).to receive(:formatted_count).with('projects').and_return('23')
|
||||
|
||||
link = search_filter_link('projects', 'Projects')
|
||||
|
||||
expect(link).to have_css('li.active')
|
||||
expect(link).to have_link('Projects', href: search_path(scope: 'projects'))
|
||||
expect(link).to have_css('span.badge.badge-pill:not(.js-search-count):not(.hidden):not([data-url])', text: '23')
|
||||
end
|
||||
|
||||
it 'renders a search filter link for another scope' do
|
||||
link = search_filter_link('projects', 'Projects')
|
||||
count_path = search_count_path(scope: 'projects')
|
||||
|
||||
expect(link).to have_css('li:not([class="active"])')
|
||||
expect(link).to have_link('Projects', href: search_path(scope: 'projects'))
|
||||
expect(link).to have_css("span.badge.badge-pill.js-search-count.hidden[data-url='#{count_path}']", text: '')
|
||||
end
|
||||
|
||||
it 'merges in the current search params and given params' do
|
||||
expect(self).to receive(:params).and_return(
|
||||
ActionController::Parameters.new(
|
||||
search: 'hello',
|
||||
scope: 'ignored',
|
||||
other_param: 'ignored'
|
||||
)
|
||||
)
|
||||
|
||||
link = search_filter_link('projects', 'Projects', search: { project_id: 23 })
|
||||
|
||||
expect(link).to have_link('Projects', href: search_path(scope: 'projects', search: 'hello', project_id: 23))
|
||||
end
|
||||
|
||||
it 'restricts the params' do
|
||||
expect(self).to receive(:params).and_return(
|
||||
ActionController::Parameters.new(
|
||||
search: 'hello',
|
||||
unknown: 42
|
||||
)
|
||||
)
|
||||
|
||||
link = search_filter_link('projects', 'Projects')
|
||||
|
||||
expect(link).to have_link('Projects', href: search_path(scope: 'projects', search: 'hello'))
|
||||
end
|
||||
|
||||
it 'assigns given data attributes on the list container' do
|
||||
link = search_filter_link('projects', 'Projects', data: { foo: 'bar' })
|
||||
|
||||
expect(link).to have_css('li[data-foo="bar"]')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#show_user_search_tab?' do
|
||||
subject { show_user_search_tab? }
|
||||
|
||||
|
@ -584,86 +641,4 @@ RSpec.describe SearchHelper do
|
|||
expect(search_sort_options).to eq(mock_created_sort)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#search_nav_tabs' do
|
||||
subject { search_nav_tabs }
|
||||
|
||||
let(:current_user) { nil }
|
||||
|
||||
before do
|
||||
allow(self).to receive(:current_user).and_return(current_user)
|
||||
end
|
||||
|
||||
context 'when @show_snippets is present' do
|
||||
before do
|
||||
@show_snippets = 1
|
||||
end
|
||||
|
||||
it { is_expected.to eq([:snippet_titles]) }
|
||||
|
||||
context 'and @project is present' do
|
||||
before do
|
||||
@project = 1
|
||||
allow(self).to receive(:project_search_tabs?).with(anything).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq([:blobs, :issues, :merge_requests, :milestones, :notes, :wiki_blobs, :commits, :users]) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when @project is present' do
|
||||
before do
|
||||
@project = 1
|
||||
end
|
||||
|
||||
context 'when user has access to project' do
|
||||
before do
|
||||
allow(self).to receive(:project_search_tabs?).with(anything).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq([:blobs, :issues, :merge_requests, :milestones, :notes, :wiki_blobs, :commits, :users]) }
|
||||
end
|
||||
|
||||
context 'when user does not have access to project' do
|
||||
before do
|
||||
allow(self).to receive(:project_search_tabs?).with(anything).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq([]) }
|
||||
end
|
||||
|
||||
context 'when user does not have access to read members for project' do
|
||||
before do
|
||||
allow(self).to receive(:project_search_tabs?).with(:members).and_return(false)
|
||||
allow(self).to receive(:project_search_tabs?).with(:merge_requests).and_return(true)
|
||||
allow(self).to receive(:project_search_tabs?).with(:milestones).and_return(true)
|
||||
allow(self).to receive(:project_search_tabs?).with(:wiki_blobs).and_return(true)
|
||||
allow(self).to receive(:project_search_tabs?).with(:issues).and_return(true)
|
||||
allow(self).to receive(:project_search_tabs?).with(:blobs).and_return(true)
|
||||
allow(self).to receive(:project_search_tabs?).with(:notes).and_return(true)
|
||||
allow(self).to receive(:project_search_tabs?).with(:commits).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq([:blobs, :issues, :merge_requests, :milestones, :notes, :wiki_blobs, :commits]) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when @show_snippets and @project are not present' do
|
||||
context 'when user has access to read users' do
|
||||
before do
|
||||
allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq([:projects, :issues, :merge_requests, :milestones, :users]) }
|
||||
end
|
||||
|
||||
context 'when user does not have access to read users' do
|
||||
before do
|
||||
allow(self).to receive(:can?).with(current_user, :read_users_list).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq([:projects, :issues, :merge_requests, :milestones]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,6 +86,16 @@ RSpec.describe Gitlab::Git::Push do
|
|||
|
||||
it { is_expected.to be_force_push }
|
||||
end
|
||||
|
||||
context 'when called muiltiple times' do
|
||||
it 'does not make make multiple calls to the force push check' do
|
||||
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).once
|
||||
|
||||
2.times do
|
||||
subject.force_push?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#branch_added?' do
|
||||
|
|
|
@ -1,32 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'boards list service' do
|
||||
context 'when parent does not have a board' do
|
||||
it 'creates a new parent board' do
|
||||
expect { service.execute }.to change(parent.boards, :count).by(1)
|
||||
end
|
||||
|
||||
it 'delegates the parent board creation to Boards::CreateService' do
|
||||
expect_any_instance_of(Boards::CreateService).to receive(:execute).once
|
||||
|
||||
service.execute
|
||||
end
|
||||
|
||||
context 'when create_default_board is false' do
|
||||
it 'does not create a new parent board' do
|
||||
expect { service.execute(create_default_board: false) }.not_to change(parent.boards, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when parent has a board' do
|
||||
before do
|
||||
create(:board, resource_parent: parent)
|
||||
end
|
||||
|
||||
it 'does not create a new board' do
|
||||
expect { service.execute }.not_to change(parent.boards, :count)
|
||||
end
|
||||
it 'does not create a new board' do
|
||||
expect { service.execute }.not_to change(parent.boards, :count)
|
||||
end
|
||||
|
||||
it 'returns parent boards' do
|
||||
|
|
|
@ -6,6 +6,7 @@ RSpec.describe 'search/show' do
|
|||
let(:search_term) { nil }
|
||||
|
||||
before do
|
||||
stub_template "search/_category.html.haml" => 'Category Partial'
|
||||
stub_template "search/_results.html.haml" => 'Results Partial'
|
||||
|
||||
@search_term = search_term
|
||||
|
@ -20,6 +21,7 @@ RSpec.describe 'search/show' do
|
|||
end
|
||||
|
||||
it 'does not render partials' do
|
||||
expect(rendered).not_to render_template('search/_category')
|
||||
expect(rendered).not_to render_template('search/_results')
|
||||
end
|
||||
end
|
||||
|
@ -28,6 +30,7 @@ RSpec.describe 'search/show' do
|
|||
let(:search_term) { 'Search Foo' }
|
||||
|
||||
it 'renders partials' do
|
||||
expect(rendered).to render_template('search/_category')
|
||||
expect(rendered).to render_template('search/_results')
|
||||
end
|
||||
|
||||
|
|
84
yarn.lock
84
yarn.lock
|
@ -1332,10 +1332,10 @@
|
|||
jest-diff "^25.2.1"
|
||||
pretty-format "^25.2.1"
|
||||
|
||||
"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5":
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
|
||||
integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
|
||||
"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
|
||||
version "7.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
|
||||
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
|
||||
|
||||
"@types/json5@^0.0.29":
|
||||
version "0.0.29"
|
||||
|
@ -1671,17 +1671,12 @@ acorn-jsx@^5.2.0, acorn-jsx@^5.3.1:
|
|||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
|
||||
integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
|
||||
|
||||
acorn-walk@^6.1.1:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
|
||||
integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
|
||||
|
||||
acorn-walk@^7.1.1:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
|
||||
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
|
||||
|
||||
acorn@^6.0.7, acorn@^6.3.0, acorn@^6.4.1:
|
||||
acorn@^6.3.0, acorn@^6.4.1:
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
|
||||
integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
|
||||
|
@ -1721,7 +1716,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
|
|||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
|
||||
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
|
||||
|
||||
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4:
|
||||
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5:
|
||||
version "6.12.6"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
|
||||
|
@ -2191,15 +2186,15 @@ babel-jest@^26.5.2:
|
|||
graceful-fs "^4.2.4"
|
||||
slash "^3.0.0"
|
||||
|
||||
babel-loader@^8.0.6:
|
||||
version "8.0.6"
|
||||
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
|
||||
integrity sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==
|
||||
babel-loader@^8.2.2:
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.2.tgz#9363ce84c10c9a40e6c753748e1441b60c8a0b81"
|
||||
integrity sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==
|
||||
dependencies:
|
||||
find-cache-dir "^2.0.0"
|
||||
loader-utils "^1.0.2"
|
||||
mkdirp "^0.5.1"
|
||||
pify "^4.0.1"
|
||||
find-cache-dir "^3.3.1"
|
||||
loader-utils "^1.4.0"
|
||||
make-dir "^3.1.0"
|
||||
schema-utils "^2.6.5"
|
||||
|
||||
babel-plugin-dynamic-import-node@^2.3.3:
|
||||
version "2.3.3"
|
||||
|
@ -3329,10 +3324,10 @@ copy-to-clipboard@^3.0.8:
|
|||
dependencies:
|
||||
toggle-selection "^1.0.6"
|
||||
|
||||
copy-webpack-plugin@^5.0.5:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz#5481a03dea1123d88a988c6ff8b78247214f0b88"
|
||||
integrity sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==
|
||||
copy-webpack-plugin@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz#8a889e1dcafa6c91c6cd4be1ad158f1d3823bae2"
|
||||
integrity sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==
|
||||
dependencies:
|
||||
cacache "^12.0.3"
|
||||
find-cache-dir "^2.1.0"
|
||||
|
@ -3344,7 +3339,7 @@ copy-webpack-plugin@^5.0.5:
|
|||
normalize-path "^3.0.0"
|
||||
p-limit "^2.2.1"
|
||||
schema-utils "^1.0.0"
|
||||
serialize-javascript "^2.1.2"
|
||||
serialize-javascript "^4.0.0"
|
||||
webpack-log "^2.0.0"
|
||||
|
||||
core-js-compat@^3.6.2:
|
||||
|
@ -5167,7 +5162,7 @@ finalhandler@~1.1.2:
|
|||
statuses "~1.5.0"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
|
||||
find-cache-dir@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
|
||||
integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
|
||||
|
@ -7984,7 +7979,7 @@ make-dir@^2.0.0, make-dir@^2.1.0:
|
|||
pify "^4.0.1"
|
||||
semver "^5.6.0"
|
||||
|
||||
make-dir@^3.0.0, make-dir@^3.0.2:
|
||||
make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
|
||||
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
|
||||
|
@ -9951,13 +9946,13 @@ raw-body@2.4.0:
|
|||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
raw-loader@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.0.tgz#d639c40fb9d72b5c7f8abc1fb2ddb25b29d3d540"
|
||||
integrity sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==
|
||||
raw-loader@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6"
|
||||
integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==
|
||||
dependencies:
|
||||
loader-utils "^1.2.3"
|
||||
schema-utils "^2.5.0"
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
rc@^1.2.8, rc@~1.2.7:
|
||||
version "1.2.8"
|
||||
|
@ -10546,7 +10541,7 @@ schema-utils@^1.0.0:
|
|||
ajv-errors "^1.0.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
|
||||
schema-utils@^2.0.0, schema-utils@^2.5.0, schema-utils@^2.7.0:
|
||||
schema-utils@^2.0.0, schema-utils@^2.5.0, schema-utils@^2.6.5, schema-utils@^2.7.0:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
|
||||
integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
|
||||
|
@ -10555,6 +10550,15 @@ schema-utils@^2.0.0, schema-utils@^2.5.0, schema-utils@^2.7.0:
|
|||
ajv "^6.12.4"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
schema-utils@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
|
||||
integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
|
||||
dependencies:
|
||||
"@types/json-schema" "^7.0.6"
|
||||
ajv "^6.12.5"
|
||||
ajv-keywords "^3.5.2"
|
||||
|
||||
scss-tokenizer@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
|
||||
|
@ -12583,13 +12587,13 @@ webidl-conversions@^6.1.0:
|
|||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
|
||||
integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
|
||||
|
||||
webpack-bundle-analyzer@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz#39b3a8f829ca044682bc6f9e011c95deb554aefd"
|
||||
integrity sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g==
|
||||
webpack-bundle-analyzer@^3.9.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz#f6f94db108fb574e415ad313de41a2707d33ef3c"
|
||||
integrity sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==
|
||||
dependencies:
|
||||
acorn "^6.0.7"
|
||||
acorn-walk "^6.1.1"
|
||||
acorn "^7.1.1"
|
||||
acorn-walk "^7.1.1"
|
||||
bfj "^6.1.1"
|
||||
chalk "^2.4.1"
|
||||
commander "^2.18.0"
|
||||
|
@ -12597,7 +12601,7 @@ webpack-bundle-analyzer@^3.6.0:
|
|||
express "^4.16.3"
|
||||
filesize "^3.6.1"
|
||||
gzip-size "^5.0.0"
|
||||
lodash "^4.17.15"
|
||||
lodash "^4.17.19"
|
||||
mkdirp "^0.5.1"
|
||||
opener "^1.5.1"
|
||||
ws "^6.0.0"
|
||||
|
|
Loading…
Reference in a new issue