Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
793d974d7c
commit
7172fb1031
|
|
@ -2,6 +2,15 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 13.11.4 (2021-05-14)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
||||
- Fix N+1 SQL queries in PipelinesController#show. !60794
|
||||
- Omit trailing slash when proxying pre-authorized routes with no suffix. !61638
|
||||
- Omit trailing slash when checking allowed requests in the read-only middleware. !61641
|
||||
|
||||
|
||||
## 13.11.3 (2021-04-30)
|
||||
|
||||
### Fixed (1 change)
|
||||
|
|
|
|||
|
|
@ -91,7 +91,9 @@ export default {
|
|||
data-testid="stuck-icon"
|
||||
/>
|
||||
|
||||
<div class="gl-display-flex gl-align-items-center">
|
||||
<div
|
||||
class="gl-display-flex gl-align-items-center gl-lg-justify-content-start gl-justify-content-end"
|
||||
>
|
||||
<div v-if="jobRef" class="gl-max-w-15 gl-text-truncate">
|
||||
<gl-icon
|
||||
v-if="createdByTag"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ const defaultTableClasses = {
|
|||
tdClass: 'gl-p-5!',
|
||||
thClass: 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!',
|
||||
};
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
const coverageTdClasses = `${defaultTableClasses.tdClass} gl-display-none! gl-lg-display-table-cell!`;
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
|
|
@ -56,7 +58,8 @@ export default {
|
|||
{
|
||||
key: 'coverage',
|
||||
label: __('Coverage'),
|
||||
...defaultTableClasses,
|
||||
tdClass: coverageTdClasses,
|
||||
thClass: defaultTableClasses.thClass,
|
||||
columnClass: 'gl-w-10p',
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { __ } from '~/locale';
|
||||
|
||||
export const DEBOUNCE_DELAY = 200;
|
||||
export const MAX_RECENT_TOKENS_SIZE = 3;
|
||||
|
||||
export const FILTER_NONE = 'None';
|
||||
export const FILTER_ANY = 'Any';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { isEmpty } from 'lodash';
|
||||
import { isEmpty, uniqWith, isEqual } from 'lodash';
|
||||
import AccessorUtilities from '~/lib/utils/accessor';
|
||||
import { queryToObject } from '~/lib/utils/url_utility';
|
||||
|
||||
import { MAX_RECENT_TOKENS_SIZE } from './constants';
|
||||
|
||||
/**
|
||||
* Strips enclosing quotations from a string if it has one.
|
||||
*
|
||||
|
|
@ -162,3 +165,38 @@ export function urlQueryToFilter(query = '') {
|
|||
return { ...memo, [filterName]: { value, operator } };
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of token values from localStorage
|
||||
* based on provided recentTokenValuesStorageKey
|
||||
*
|
||||
* @param {String} recentTokenValuesStorageKey
|
||||
* @returns
|
||||
*/
|
||||
export function getRecentlyUsedTokenValues(recentTokenValuesStorageKey) {
|
||||
let recentlyUsedTokenValues = [];
|
||||
if (AccessorUtilities.isLocalStorageAccessSafe()) {
|
||||
recentlyUsedTokenValues = JSON.parse(localStorage.getItem(recentTokenValuesStorageKey)) || [];
|
||||
}
|
||||
return recentlyUsedTokenValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets provided token value to recently used array
|
||||
* within localStorage for provided recentTokenValuesStorageKey
|
||||
*
|
||||
* @param {String} recentTokenValuesStorageKey
|
||||
* @param {Object} tokenValue
|
||||
*/
|
||||
export function setTokenValueToRecentlyUsed(recentTokenValuesStorageKey, tokenValue) {
|
||||
const recentlyUsedTokenValues = getRecentlyUsedTokenValues(recentTokenValuesStorageKey);
|
||||
|
||||
recentlyUsedTokenValues.splice(0, 0, { ...tokenValue });
|
||||
|
||||
if (AccessorUtilities.isLocalStorageAccessSafe()) {
|
||||
localStorage.setItem(
|
||||
recentTokenValuesStorageKey,
|
||||
JSON.stringify(uniqWith(recentlyUsedTokenValues, isEqual).slice(0, MAX_RECENT_TOKENS_SIZE)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
<script>
|
||||
import {
|
||||
GlFilteredSearchToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlDropdownSectionHeader,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
|
||||
import { DEBOUNCE_DELAY } from '../constants';
|
||||
import { getRecentlyUsedTokenValues, setTokenValueToRecentlyUsed } from '../filtered_search_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlFilteredSearchToken,
|
||||
GlFilteredSearchSuggestion,
|
||||
GlDropdownDivider,
|
||||
GlDropdownSectionHeader,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
tokenConfig: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
tokenValue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
tokenActive: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
tokensListLoading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
tokenValues: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
fnActiveTokenValue: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
defaultTokenValues: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
recentTokenValuesStorageKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
valueIdentifier: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'id',
|
||||
},
|
||||
fnCurrentTokenValue: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchKey: '',
|
||||
recentTokenValues: this.recentTokenValuesStorageKey
|
||||
? getRecentlyUsedTokenValues(this.recentTokenValuesStorageKey)
|
||||
: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isRecentTokenValuesEnabled() {
|
||||
return Boolean(this.recentTokenValuesStorageKey);
|
||||
},
|
||||
recentTokenIds() {
|
||||
return this.recentTokenValues.map((tokenValue) => tokenValue.id || tokenValue.name);
|
||||
},
|
||||
currentTokenValue() {
|
||||
if (this.fnCurrentTokenValue) {
|
||||
return this.fnCurrentTokenValue(this.tokenValue.data);
|
||||
}
|
||||
return this.tokenValue.data.toLowerCase();
|
||||
},
|
||||
activeTokenValue() {
|
||||
return this.fnActiveTokenValue(this.tokenValues, this.currentTokenValue);
|
||||
},
|
||||
/**
|
||||
* Return all the tokenValues when searchKey is present
|
||||
* otherwise return only the tokenValues which aren't
|
||||
* present in "Recently used"
|
||||
*/
|
||||
availableTokenValues() {
|
||||
return this.searchKey
|
||||
? this.tokenValues
|
||||
: this.tokenValues.filter(
|
||||
(tokenValue) => !this.recentTokenIds.includes(tokenValue[this.valueIdentifier]),
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
tokenActive: {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
if (!newValue && !this.tokenValues.length) {
|
||||
this.$emit('fetch-token-values', this.tokenValue.data);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleInput({ data }) {
|
||||
this.searchKey = data;
|
||||
setTimeout(() => {
|
||||
if (!this.tokensListLoading) this.$emit('fetch-token-values', data);
|
||||
}, DEBOUNCE_DELAY);
|
||||
},
|
||||
handleTokenValueSelected(activeTokenValue) {
|
||||
if (this.isRecentTokenValuesEnabled) {
|
||||
setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-filtered-search-token
|
||||
:config="tokenConfig"
|
||||
v-bind="{ ...this.$parent.$props, ...this.$parent.$attrs }"
|
||||
v-on="this.$parent.$listeners"
|
||||
@input="handleInput"
|
||||
@select="handleTokenValueSelected(activeTokenValue)"
|
||||
>
|
||||
<template #view-token="viewTokenProps">
|
||||
<slot name="view-token" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
|
||||
</template>
|
||||
<template #view="viewTokenProps">
|
||||
<slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
|
||||
</template>
|
||||
<template #suggestions>
|
||||
<template v-if="defaultTokenValues.length">
|
||||
<gl-filtered-search-suggestion
|
||||
v-for="token in defaultTokenValues"
|
||||
:key="token.value"
|
||||
:value="token.value"
|
||||
>
|
||||
{{ token.text }}
|
||||
</gl-filtered-search-suggestion>
|
||||
<gl-dropdown-divider />
|
||||
</template>
|
||||
<template v-if="isRecentTokenValuesEnabled && recentTokenValues.length && !searchKey">
|
||||
<gl-dropdown-section-header>{{ __('Recently used') }}</gl-dropdown-section-header>
|
||||
<slot name="token-values-list" :token-values="recentTokenValues"></slot>
|
||||
<gl-dropdown-divider />
|
||||
</template>
|
||||
<gl-loading-icon v-if="tokensListLoading" />
|
||||
<template v-else>
|
||||
<slot name="token-values-list" :token-values="availableTokenValues"></slot>
|
||||
</template>
|
||||
</template>
|
||||
</gl-filtered-search-token>
|
||||
</template>
|
||||
|
|
@ -148,6 +148,27 @@ module CommitsHelper
|
|||
end
|
||||
end
|
||||
|
||||
# This is used to calculate a cache key for the app/views/projects/commits/_commit.html.haml
|
||||
# partial. It takes some of the same parameters as used in the partial and will hash the
|
||||
# current pipeline status.
|
||||
#
|
||||
# This includes a keyed hash for values that can be nil, to prevent invalid cache entries
|
||||
# being served if the order should change in future.
|
||||
def commit_partial_cache_key(commit, ref:, merge_request:, request:)
|
||||
[
|
||||
commit,
|
||||
commit.author,
|
||||
ref,
|
||||
{
|
||||
merge_request: merge_request,
|
||||
pipeline_status: hashed_pipeline_status(commit, ref),
|
||||
xhr: request.xhr?,
|
||||
controller: controller.controller_path,
|
||||
path: @path # referred to in #link_to_browse_code
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Private: Returns a link to a person. If the person has a matching user and
|
||||
|
|
@ -221,4 +242,14 @@ module CommitsHelper
|
|||
project_commit_path(project, commit)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hashed_pipeline_status(commit, ref)
|
||||
status = commit.status_for(ref)
|
||||
|
||||
return if status.nil?
|
||||
|
||||
Digest::SHA1.hexdigest(status.to_s)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ module NotifyHelper
|
|||
when "Developer"
|
||||
s_("InviteEmail|As a developer, you have full access to projects, so you can take an idea from concept to production.")
|
||||
when "Maintainer"
|
||||
s_("InviteEmail|As a maintainer, you have full access to projects. You can push commits to master and deploy to production.")
|
||||
s_("InviteEmail|As a maintainer, you have full access to projects. You can push commits to the default branch and deploy to production.")
|
||||
when "Owner"
|
||||
s_("InviteEmail|As an owner, you have full access to projects and can manage access to the group, including inviting new members.")
|
||||
when "Minimal Access"
|
||||
|
|
|
|||
|
|
@ -2539,7 +2539,7 @@ class Project < ApplicationRecord
|
|||
def default_branch_or_main
|
||||
return default_branch if default_branch
|
||||
|
||||
Gitlab::DefaultBranch.value(project: self)
|
||||
Gitlab::DefaultBranch.value(object: self)
|
||||
end
|
||||
|
||||
def ci_config_path_or_default
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ class Snippet < ApplicationRecord
|
|||
|
||||
override :default_branch
|
||||
def default_branch
|
||||
super || Gitlab::DefaultBranch.value(project: project)
|
||||
super || Gitlab::DefaultBranch.value(object: project)
|
||||
end
|
||||
|
||||
def repository_storage
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-default-branch-name'), html: { class: 'fieldset-form' } do |f|
|
||||
= form_errors(@application_setting)
|
||||
|
||||
- fallback_branch_name = '<code>master</code>'
|
||||
- fallback_branch_name = "<code>#{Gitlab::DefaultBranch.value}</code>"
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :default_branch_name, _('Default initial branch name'), class: 'label-light'
|
||||
= f.text_field :default_branch_name, placeholder: 'master', class: 'form-control gl-form-input'
|
||||
= f.text_field :default_branch_name, placeholder: Gitlab::DefaultBranch.value, class: 'form-control gl-form-input'
|
||||
%span.form-text.text-muted
|
||||
= (_("Changes affect new repositories only. If not specified, Git's default name %{branch_name_default} will be used.") % { branch_name_default: fallback_branch_name } ).html_safe
|
||||
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@
|
|||
.settings-content
|
||||
= form_for @group, url: group_path(@group, anchor: 'js-default-branch-name'), html: { class: 'fieldset-form' } do |f|
|
||||
= form_errors(@group)
|
||||
- fallback_branch_name = '<code>master</code>'
|
||||
- fallback_branch_name = "<code>#{Gitlab::DefaultBranch.value(object: @group)}</code>"
|
||||
|
||||
%fieldset
|
||||
.form-group
|
||||
= f.label :default_branch_name, _('Default initial branch name'), class: 'label-light'
|
||||
= f.text_field :default_branch_name, value: group.namespace_settings&.default_branch_name, placeholder: 'master', class: 'form-control'
|
||||
= f.text_field :default_branch_name, value: group.namespace_settings&.default_branch_name, placeholder: Gitlab::DefaultBranch.value(object: @group), class: 'form-control'
|
||||
%span.form-text.text-muted
|
||||
= (_("Changes affect new repositories only. If not specified, either the configured application-wide default or Git's default name %{branch_name_default} will be used.") % { branch_name_default: fallback_branch_name }).html_safe
|
||||
|
||||
|
|
|
|||
|
|
@ -3,22 +3,24 @@
|
|||
- `assets/javascripts/diffs/components/commit_item.vue`
|
||||
|
||||
EXCEPTION WARNING - see above `.vue` file for de-sync drift
|
||||
|
||||
WARNING: When introducing new content here, please consider what
|
||||
changes may need to be made in the cache keys used to
|
||||
wrap this view, found in
|
||||
CommitsHelper#commit_partial_cache_key
|
||||
-#-----------------------------------------------------------------
|
||||
- view_details = local_assigns.fetch(:view_details, false)
|
||||
- merge_request = local_assigns.fetch(:merge_request, nil)
|
||||
- project = local_assigns.fetch(:project) { merge_request&.project }
|
||||
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
|
||||
- commit = commit.present(current_user: current_user)
|
||||
- commit_status = commit.status_for(ref)
|
||||
- collapsible = local_assigns.fetch(:collapsible, true)
|
||||
- link_data_attrs = local_assigns.fetch(:link_data_attrs, {})
|
||||
|
||||
- link = commit_path(project, commit, merge_request: merge_request)
|
||||
|
||||
- view_details = local_assigns.fetch(:view_details, false)
|
||||
- merge_request = local_assigns.fetch(:merge_request, nil)
|
||||
- project = local_assigns.fetch(:project) { merge_request&.project }
|
||||
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
|
||||
- commit = commit.present(current_user: current_user)
|
||||
- commit_status = commit.status_for(ref)
|
||||
- collapsible = local_assigns.fetch(:collapsible, true)
|
||||
- link_data_attrs = local_assigns.fetch(:link_data_attrs, {})
|
||||
- link = commit_path(project, commit, merge_request: merge_request)
|
||||
- show_project_name = local_assigns.fetch(:show_project_name, false)
|
||||
|
||||
%li{ class: ["commit flex-row", ("js-toggle-container" if collapsible)], id: "commit-#{commit.short_id}" }
|
||||
|
||||
.avatar-cell.d-none.d-sm-block
|
||||
= author_avatar(commit, size: 40, has_tooltip: false)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
|
||||
- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
|
||||
|
||||
- commits = @commits
|
||||
- context_commits = @context_commits
|
||||
- commits = @commits&.map { |commit| commit.present(current_user: current_user) }
|
||||
- context_commits = @context_commits&.map { |commit| commit.present(current_user: current_user) }
|
||||
- hidden = @hidden_commit_count
|
||||
|
||||
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, daily_commits|
|
||||
|
|
@ -14,7 +14,10 @@
|
|||
|
||||
%li.commits-row{ data: { day: day } }
|
||||
%ul.content-list.commit-list.flex-list
|
||||
= render partial: 'projects/commits/commit', collection: daily_commits, locals: { project: project, ref: ref, merge_request: merge_request }
|
||||
- if Feature.enabled?(:cached_commits, project, default_enabled: :yaml)
|
||||
= render partial: 'projects/commits/commit', collection: daily_commits, locals: { project: project, ref: ref, merge_request: merge_request }, cached: -> (commit) { commit_partial_cache_key(commit, ref: ref, merge_request: merge_request, request: request) }
|
||||
- else
|
||||
= render partial: 'projects/commits/commit', collection: daily_commits, locals: { project: project, ref: ref, merge_request: merge_request }
|
||||
|
||||
- if context_commits.present?
|
||||
%li.commit-header.js-commit-header
|
||||
|
|
@ -25,7 +28,10 @@
|
|||
|
||||
%li.commits-row
|
||||
%ul.content-list.commit-list.flex-list
|
||||
= render partial: 'projects/commits/commit', collection: context_commits, locals: { project: project, ref: ref, merge_request: merge_request }
|
||||
- if Feature.enabled?(:cached_commits, project, default_enabled: :yaml)
|
||||
= render partial: 'projects/commits/commit', collection: context_commits, locals: { project: project, ref: ref, merge_request: merge_request }, cached: -> (commit) { commit_partial_cache_key(commit, ref: ref, merge_request: merge_request, request: request) }
|
||||
- else
|
||||
= render partial: 'projects/commits/commit', collection: context_commits, locals: { project: project, ref: ref, merge_request: merge_request }
|
||||
|
||||
- if hidden > 0
|
||||
%li.gl-alert.gl-alert-warning
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@
|
|||
%h3.page-title
|
||||
= _("Compare Git revisions")
|
||||
.sub-header-block
|
||||
- example_master = capture do
|
||||
%code.ref-name master
|
||||
- example_branch = capture do
|
||||
%code.ref-name= @project.default_branch_or_main
|
||||
- example_sha = capture do
|
||||
%code.ref-name 4eedf23
|
||||
= html_escape(_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.")) % { master: example_master.html_safe, sha: example_sha.html_safe }
|
||||
= html_escape(_("Choose a branch/tag (e.g. %{branch}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.")) % { branch: example_branch.html_safe, sha: example_sha.html_safe }
|
||||
%br
|
||||
= html_escape(_("Changes are shown as if the %{b_open}source%{b_close} revision was being merged into the %{b_open}target%{b_close} revision.")) % { b_open: '<b>'.html_safe, b_close: '</b>'.html_safe }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Omit trailing slash when checking allowed requests in the read-only middleware
|
||||
merge_request: 61641
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Rename master to main in views placeholders
|
||||
merge_request: 61252
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Omit trailing slash when proxying pre-authorized routes with no suffix
|
||||
merge_request: 61638
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
title: Fix N+1 SQL queries in PipelinesController#show
|
||||
merge_request: 60794
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: cached_commits
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61617
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330968
|
||||
milestone: '13.12'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: false
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const CACHE_PATHS = [
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/* eslint-disable no-inner-declarations, no-param-reassign */
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const argumentsParser = require('commander');
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const IS_EE = require('./helpers/is_ee_env');
|
||||
const webpackConfig = require('./webpack.config.js');
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const SOURCEGRAPH_VERSION = require('@sourcegraph/code-host-integration/package.json').version;
|
||||
|
||||
const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
||||
const VUE_LOADER_VERSION = require('vue-loader/package.json').version;
|
||||
const VUE_VERSION = require('vue/package.json').version;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ except `main` and branches that start with `release/`.
|
|||
|
||||
### `only: variables` / `except: variables` examples
|
||||
|
||||
You can use `except:variables` to exclude jobs based on a commit message:
|
||||
You can use [`except:variables`](../yaml/README.md#onlyvariables--exceptvariables) to exclude jobs based on a commit message:
|
||||
|
||||
```yaml
|
||||
end-to-end:
|
||||
|
|
@ -223,6 +223,48 @@ test:
|
|||
- "README.md"
|
||||
```
|
||||
|
||||
## Use predefined CI/CD variables to run jobs only in specific pipeline types
|
||||
|
||||
You can use [predefined CI/CD variables](../variables/predefined_variables.md) to choose
|
||||
which pipeline types jobs run in, with:
|
||||
|
||||
- [`rules`](../yaml/README.md#rules)
|
||||
- [`only:variables`](../yaml/README.md#onlyvariables--exceptvariables)
|
||||
- [`except:variables`](../yaml/README.md#onlyvariables--exceptvariables)
|
||||
|
||||
The following table lists some of the variables that you can use, and the pipeline
|
||||
types the variables can control for:
|
||||
|
||||
- Branch pipelines that run for Git `push` events to a branch, like new commits or tags.
|
||||
- Tag pipelines that run only when a new Git tag is pushed to a branch.
|
||||
- [Merge request pipelines](../merge_request_pipelines/index.md) that run for changes
|
||||
to a merge request, like new commits or selecting the **Run pipeline** button
|
||||
in a merge request's pipelines tab.
|
||||
- [Scheduled pipelines](../pipelines/schedules.md).
|
||||
|
||||
| Variables | Branch | Tag | Merge request | Scheduled |
|
||||
|--------------------------------------------|--------|-----|---------------|-----------|
|
||||
| `CI_COMMIT_BRANCH` | Yes | | | Yes |
|
||||
| `CI_COMMIT_TAG` | | Yes | | Yes, if the scheduled pipeline is configured to run on a tag. |
|
||||
| `CI_PIPELINE_SOURCE = push` | Yes | Yes | | |
|
||||
| `CI_PIPELINE_SOURCE = scheduled` | | | | Yes |
|
||||
| `CI_PIPELINE_SOURCE = merge_request_event` | | | Yes | |
|
||||
| `CI_MERGE_REQUEST_IID` | | | Yes | |
|
||||
|
||||
For example, to configure a job to run for merge request pipelines and scheduled pipelines,
|
||||
but not branch or tag pipelines:
|
||||
|
||||
```yaml
|
||||
job1:
|
||||
script:
|
||||
- echo
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_PIPELINE_SOURCE == "scheduled"
|
||||
- if: $CI_PIPELINE_SOURCE == "push"
|
||||
when: never
|
||||
```
|
||||
|
||||
## Regular expressions
|
||||
|
||||
The `@` symbol denotes the beginning of a ref's repository path.
|
||||
|
|
|
|||
|
|
@ -36,14 +36,14 @@ The keywords available for jobs are:
|
|||
| [`coverage`](#coverage) | Code coverage settings for a given job. |
|
||||
| [`dependencies`](#dependencies) | Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from. |
|
||||
| [`environment`](#environment) | Name of an environment to which the job deploys. |
|
||||
| [`except`](#only--except) | Control when jobs are not created. |
|
||||
| [`except`](#only--except) | Control when jobs are not created. |
|
||||
| [`extends`](#extends) | Configuration entries that this job inherits from. |
|
||||
| [`image`](#image) | Use Docker images. |
|
||||
| [`include`](#include) | Include external YAML files. |
|
||||
| [`inherit`](#inherit) | Select which global defaults all jobs inherit. |
|
||||
| [`interruptible`](#interruptible) | Defines if a job can be canceled when made redundant by a newer run. |
|
||||
| [`needs`](#needs) | Execute jobs earlier than the stage ordering. |
|
||||
| [`only`](#only--except) | Control when jobs are created. |
|
||||
| [`only`](#only--except) | Control when jobs are created. |
|
||||
| [`pages`](#pages) | Upload the result of a job to use with GitLab Pages. |
|
||||
| [`parallel`](#parallel) | How many instances of a job should be run in parallel. |
|
||||
| [`release`](#release) | Instructs the runner to generate a [release](../../user/project/releases/index.md) object. |
|
||||
|
|
@ -1615,7 +1615,7 @@ Four keywords can be used with `only` and `except`:
|
|||
- [`changes`](#onlychanges--exceptchanges)
|
||||
- [`kubernetes`](#onlykubernetes--exceptkubernetes)
|
||||
|
||||
See [control jobs with `only` and `except`](../jobs/job_control.md#specify-when-jobs-run-with-only-and-except)
|
||||
See [specify when jobs run with `only` and `except`](../jobs/job_control.md#specify-when-jobs-run-with-only-and-except)
|
||||
for more details and examples.
|
||||
|
||||
#### `only:refs` / `except:refs`
|
||||
|
|
@ -3632,10 +3632,10 @@ failure.
|
|||
`artifacts:when` can be set to one of the following values:
|
||||
|
||||
1. `on_success` (default): Upload artifacts only when the job succeeds.
|
||||
1. `on_failure`: Upload artifacts only when the job fails. Useful, for example, when
|
||||
1. `on_failure`: Upload artifacts only when the job fails.
|
||||
1. `always`: Always upload artifacts. Useful, for example, when
|
||||
[uploading artifacts](../unit_test_reports.md#viewing-junit-screenshots-on-gitlab) required to
|
||||
troubleshoot failing tests.
|
||||
1. `always`: Always upload artifacts.
|
||||
|
||||
For example, to upload artifacts only when a job fails:
|
||||
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ component has 2 indicators:
|
|||
The calculation to a ratio then happens as follows:
|
||||
|
||||
```math
|
||||
\frac {operations\_meeting\_apdex + (total\_operations - operations\_with_\errors)} {total\_apdex\_measurements + total\_operations}
|
||||
\frac {operations\_meeting\_apdex + (total\_operations - operations\_with\_errors)} {total\_apdex\_measurements + total\_operations}
|
||||
```
|
||||
|
||||
*Caveat:* Not all components are included, causing the
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ To remove a deprecated metric:
|
|||
[fixtures](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/master/spec/support/usage_data_helpers.rb#L540)
|
||||
used to test
|
||||
[`UsageDataController#create`](https://gitlab.com/gitlab-services/version-gitlab-com/-/blob/3760ef28/spec/controllers/usage_data_controller_spec.rb#L75)
|
||||
endpoint, and assure that test suite does not fail when metric that you wish to remove is not included into test payload.
|
||||
endpoint, and assure that test suite does not fail when metric that you wish to remove is not included into test payload.
|
||||
|
||||
1. Create an issue in the
|
||||
[GitLab Data Team project](https://gitlab.com/gitlab-data/analytics/-/issues).
|
||||
|
|
@ -276,7 +276,7 @@ To remove a deprecated metric:
|
|||
This step can be skipped if verification done during [deprecation process](#3-deprecate-a-metric)
|
||||
reported that metric is not required by any data transformation in Snowflake data warehouse nor it is
|
||||
used by any of SiSense dashboards.
|
||||
|
||||
|
||||
1. After you verify the metric can be safely removed,
|
||||
update the attributes of the metric's YAML definition:
|
||||
|
||||
|
|
@ -1024,7 +1024,13 @@ On GitLab.com, we have DangerBot setup to monitor Product Intelligence related f
|
|||
|
||||
### 10. Verify your metric
|
||||
|
||||
On GitLab.com, the Product Intelligence team regularly monitors Usage Ping. They may alert you that your metrics need further optimization to run quicker and with greater success. You may also use the [Usage Ping QA dashboard](https://app.periscopedata.com/app/gitlab/632033/Usage-Ping-QA) to check how well your metric performs. The dashboard allows filtering by GitLab version, by "Self-managed" & "SaaS" and shows you how many failures have occurred for each metric. Whenever you notice a high failure rate, you may re-optimize your metric.
|
||||
On GitLab.com, the Product Intelligence team regularly [monitors Usage Ping](https://gitlab.com/groups/gitlab-org/-/epics/6000).
|
||||
They may alert you that your metrics need further optimization to run quicker and with greater success.
|
||||
|
||||
The Usage Ping JSON payload for GitLab.com is shared in the
|
||||
[#g_product_intelligence](https://gitlab.slack.com/archives/CL3A7GFPF) Slack channel every week.
|
||||
|
||||
You may also use the [Usage Ping QA dashboard](https://app.periscopedata.com/app/gitlab/632033/Usage-Ping-QA) to check how well your metric performs. The dashboard allows filtering by GitLab version, by "Self-managed" & "SaaS" and shows you how many failures have occurred for each metric. Whenever you notice a high failure rate, you may re-optimize your metric.
|
||||
|
||||
### Usage Ping local setup
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
# Read more about the feature here: https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html
|
||||
|
||||
browser_performance:
|
||||
stage: performance
|
||||
image: docker:19.03.12
|
||||
allow_failure: true
|
||||
variables:
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
SITESPEED_IMAGE: sitespeedio/sitespeed.io
|
||||
SITESPEED_VERSION: 14.1.0
|
||||
SITESPEED_OPTIONS: ''
|
||||
services:
|
||||
- docker:19.03.12-dind
|
||||
script:
|
||||
- |
|
||||
if ! docker info &>/dev/null; then
|
||||
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
|
||||
export DOCKER_HOST='tcp://localhost:2375'
|
||||
fi
|
||||
fi
|
||||
- export CI_ENVIRONMENT_URL=$(cat environment_url.txt)
|
||||
- mkdir gitlab-exporter
|
||||
# Busybox wget does not support proxied HTTPS, get the real thing.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/287611.
|
||||
- (env | grep -i _proxy= 2>&1 >/dev/null) && apk --no-cache add wget
|
||||
- wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/1.1.0/index.js
|
||||
- mkdir sitespeed-results
|
||||
- |
|
||||
function propagate_env_vars() {
|
||||
CURRENT_ENV=$(printenv)
|
||||
|
||||
for VAR_NAME; do
|
||||
echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME "
|
||||
done
|
||||
}
|
||||
- |
|
||||
if [ -f .gitlab-urls.txt ]
|
||||
then
|
||||
sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt
|
||||
docker run \
|
||||
$(propagate_env_vars \
|
||||
auto_proxy \
|
||||
https_proxy \
|
||||
http_proxy \
|
||||
no_proxy \
|
||||
AUTO_PROXY \
|
||||
HTTPS_PROXY \
|
||||
HTTP_PROXY \
|
||||
NO_PROXY \
|
||||
) \
|
||||
--shm-size=1g --rm -v "$(pwd)":/sitespeed.io $SITESPEED_IMAGE:$SITESPEED_VERSION --plugins.add ./gitlab-exporter --cpu --outputFolder sitespeed-results .gitlab-urls.txt $SITESPEED_OPTIONS
|
||||
else
|
||||
docker run \
|
||||
$(propagate_env_vars \
|
||||
auto_proxy \
|
||||
https_proxy \
|
||||
http_proxy \
|
||||
no_proxy \
|
||||
AUTO_PROXY \
|
||||
HTTPS_PROXY \
|
||||
HTTP_PROXY \
|
||||
NO_PROXY \
|
||||
) \
|
||||
--shm-size=1g --rm -v "$(pwd)":/sitespeed.io $SITESPEED_IMAGE:$SITESPEED_VERSION --plugins.add ./gitlab-exporter --cpu --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL" $SITESPEED_OPTIONS
|
||||
fi
|
||||
- mv sitespeed-results/data/performance.json browser-performance.json
|
||||
artifacts:
|
||||
paths:
|
||||
- sitespeed-results/
|
||||
reports:
|
||||
browser_performance: browser-performance.json
|
||||
rules:
|
||||
- if: '$CI_KUBERNETES_ACTIVE == null || $CI_KUBERNETES_ACTIVE == ""'
|
||||
when: never
|
||||
- if: '$PERFORMANCE_DISABLED'
|
||||
when: never
|
||||
- if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
# Class is used while we're migrating from master to main
|
||||
module Gitlab
|
||||
module DefaultBranch
|
||||
def self.value(project: nil)
|
||||
Feature.enabled?(:main_branch_over_master, project, default_enabled: :yaml) ? 'main' : 'master'
|
||||
def self.value(object: nil)
|
||||
Feature.enabled?(:main_branch_over_master, object, default_enabled: :yaml) ? 'main' : 'master'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6334,7 +6334,7 @@ msgstr ""
|
|||
msgid "Choose File..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
|
||||
msgid "Choose a branch/tag (e.g. %{branch}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request."
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose a file"
|
||||
|
|
@ -17894,7 +17894,7 @@ msgstr ""
|
|||
msgid "InviteEmail|As a guest, you can view projects, leave comments, and create issues."
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteEmail|As a maintainer, you have full access to projects. You can push commits to master and deploy to production."
|
||||
msgid "InviteEmail|As a maintainer, you have full access to projects. You can push commits to the default branch and deploy to production."
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteEmail|As a reporter, you can view projects and reports, and leave comments on issues."
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.10.1",
|
||||
"@gitlab/eslint-plugin": "8.3.0",
|
||||
"@gitlab/eslint-plugin": "8.4.0",
|
||||
"@gitlab/stylelint-config": "2.3.0",
|
||||
"@testing-library/dom": "^7.16.2",
|
||||
"@vue/test-utils": "1.1.2",
|
||||
|
|
@ -201,7 +201,7 @@
|
|||
"docdash": "^1.0.2",
|
||||
"eslint": "7.26.0",
|
||||
"eslint-import-resolver-jest": "3.0.0",
|
||||
"eslint-import-resolver-webpack": "0.13.0",
|
||||
"eslint-import-resolver-webpack": "0.13.1",
|
||||
"eslint-plugin-jasmine": "4.1.2",
|
||||
"eslint-plugin-no-jquery": "2.6.0",
|
||||
"gettext-extractor": "^3.5.3",
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ if (process.env.RAILS_ENV !== 'production') {
|
|||
}
|
||||
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const pjs = require('postcss');
|
||||
|
||||
const paths = glob.sync('public/assets/page_bundles/_mixins_and_variables_and_functions*.css', {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
const { resolve } = require('path');
|
||||
const { sync } = require('glob');
|
||||
const { createCoverageMap } = require('istanbul-lib-coverage');
|
||||
const { createContext } = require('istanbul-lib-report');
|
||||
const { create } = require('istanbul-reports');
|
||||
const { resolve } = require('path');
|
||||
|
||||
const coverageMap = createCoverageMap();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
|
||||
import AccessorUtilities from '~/lib/utils/accessor';
|
||||
import {
|
||||
stripQuotes,
|
||||
uniqueTokens,
|
||||
|
|
@ -5,6 +8,8 @@ import {
|
|||
processFilters,
|
||||
filterToQueryObject,
|
||||
urlQueryToFilter,
|
||||
getRecentlyUsedTokenValues,
|
||||
setTokenValueToRecentlyUsed,
|
||||
} from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
|
||||
|
||||
import {
|
||||
|
|
@ -14,6 +19,12 @@ import {
|
|||
tokenValuePlain,
|
||||
} from './mock_data';
|
||||
|
||||
const mockStorageKey = 'recent-tokens';
|
||||
|
||||
function setLocalStorageAvailability(isAvailable) {
|
||||
jest.spyOn(AccessorUtilities, 'isLocalStorageAccessSafe').mockReturnValue(isAvailable);
|
||||
}
|
||||
|
||||
describe('Filtered Search Utils', () => {
|
||||
describe('stripQuotes', () => {
|
||||
it.each`
|
||||
|
|
@ -249,3 +260,79 @@ describe('urlQueryToFilter', () => {
|
|||
expect(res).toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRecentlyUsedTokenValues', () => {
|
||||
useLocalStorageSpy();
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.removeItem(mockStorageKey);
|
||||
});
|
||||
|
||||
it('returns array containing recently used token values from provided recentTokenValuesStorageKey', () => {
|
||||
setLocalStorageAvailability(true);
|
||||
|
||||
const mockExpectedArray = [{ foo: 'bar' }];
|
||||
localStorage.setItem(mockStorageKey, JSON.stringify(mockExpectedArray));
|
||||
|
||||
expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual(mockExpectedArray);
|
||||
});
|
||||
|
||||
it('returns empty array when provided recentTokenValuesStorageKey does not have anything in localStorage', () => {
|
||||
setLocalStorageAvailability(true);
|
||||
|
||||
expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns empty array when when access to localStorage is not available', () => {
|
||||
setLocalStorageAvailability(false);
|
||||
|
||||
expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTokenValueToRecentlyUsed', () => {
|
||||
const mockTokenValue1 = { foo: 'bar' };
|
||||
const mockTokenValue2 = { bar: 'baz' };
|
||||
useLocalStorageSpy();
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.removeItem(mockStorageKey);
|
||||
});
|
||||
|
||||
it('adds provided tokenValue to localStorage for recentTokenValuesStorageKey', () => {
|
||||
setLocalStorageAvailability(true);
|
||||
|
||||
setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
|
||||
|
||||
expect(JSON.parse(localStorage.getItem(mockStorageKey))).toEqual([mockTokenValue1]);
|
||||
});
|
||||
|
||||
it('adds provided tokenValue to localStorage at the top of existing values (i.e. Stack order)', () => {
|
||||
setLocalStorageAvailability(true);
|
||||
|
||||
setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
|
||||
setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue2);
|
||||
|
||||
expect(JSON.parse(localStorage.getItem(mockStorageKey))).toEqual([
|
||||
mockTokenValue2,
|
||||
mockTokenValue1,
|
||||
]);
|
||||
});
|
||||
|
||||
it('ensures that provided tokenValue is not added twice', () => {
|
||||
setLocalStorageAvailability(true);
|
||||
|
||||
setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
|
||||
setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
|
||||
|
||||
expect(JSON.parse(localStorage.getItem(mockStorageKey))).toEqual([mockTokenValue1]);
|
||||
});
|
||||
|
||||
it('does not add any value when acess to localStorage is not available', () => {
|
||||
setLocalStorageAvailability(false);
|
||||
|
||||
setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1);
|
||||
|
||||
expect(JSON.parse(localStorage.getItem(mockStorageKey))).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
import { GlFilteredSearchToken } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import {
|
||||
mockRegularLabel,
|
||||
mockLabels,
|
||||
} from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
|
||||
|
||||
import { DEFAULT_LABELS } from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import {
|
||||
getRecentlyUsedTokenValues,
|
||||
setTokenValueToRecentlyUsed,
|
||||
} from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
|
||||
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
|
||||
|
||||
import { mockLabelToken } from '../mock_data';
|
||||
|
||||
jest.mock('~/vue_shared/components/filtered_search_bar/filtered_search_utils');
|
||||
|
||||
const mockStorageKey = 'recent-tokens-label_name';
|
||||
|
||||
const defaultStubs = {
|
||||
Portal: true,
|
||||
GlFilteredSearchToken: {
|
||||
template: `
|
||||
<div>
|
||||
<slot name="view-token"></slot>
|
||||
<slot name="view"></slot>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
GlFilteredSearchSuggestionList: {
|
||||
template: '<div></div>',
|
||||
methods: {
|
||||
getValue: () => '=',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const defaultSlots = {
|
||||
'view-token': `
|
||||
<div class="js-view-token">${mockRegularLabel.title}</div>
|
||||
`,
|
||||
view: `
|
||||
<div class="js-view">${mockRegularLabel.title}</div>
|
||||
`,
|
||||
};
|
||||
|
||||
const mockProps = {
|
||||
tokenConfig: mockLabelToken,
|
||||
tokenValue: { data: '' },
|
||||
tokenActive: false,
|
||||
tokensListLoading: false,
|
||||
tokenValues: [],
|
||||
fnActiveTokenValue: jest.fn(),
|
||||
defaultTokenValues: DEFAULT_LABELS,
|
||||
recentTokenValuesStorageKey: mockStorageKey,
|
||||
fnCurrentTokenValue: jest.fn(),
|
||||
};
|
||||
|
||||
function createComponent({
|
||||
props = { ...mockProps },
|
||||
stubs = defaultStubs,
|
||||
slots = defaultSlots,
|
||||
} = {}) {
|
||||
return mount(BaseToken, {
|
||||
propsData: {
|
||||
...props,
|
||||
},
|
||||
provide: {
|
||||
portalName: 'fake target',
|
||||
alignSuggestions: jest.fn(),
|
||||
suggestionsListClass: 'custom-class',
|
||||
},
|
||||
stubs,
|
||||
slots,
|
||||
});
|
||||
}
|
||||
|
||||
describe('BaseToken', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent({
|
||||
props: {
|
||||
...mockProps,
|
||||
tokenValue: { data: `"${mockRegularLabel.title}"` },
|
||||
tokenValues: mockLabels,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('data', () => {
|
||||
it('calls `getRecentlyUsedTokenValues` to populate `recentTokenValues` when `recentTokenValuesStorageKey` is defined', () => {
|
||||
expect(getRecentlyUsedTokenValues).toHaveBeenCalledWith(mockStorageKey);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('currentTokenValue', () => {
|
||||
it('calls `fnCurrentTokenValue` when it is provided', () => {
|
||||
// We're disabling lint to trigger computed prop execution for this test.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { currentTokenValue } = wrapper.vm;
|
||||
|
||||
expect(wrapper.vm.fnCurrentTokenValue).toHaveBeenCalledWith(`"${mockRegularLabel.title}"`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeTokenValue', () => {
|
||||
it('calls `fnActiveTokenValue` when it is provided', async () => {
|
||||
wrapper.setProps({
|
||||
fnCurrentTokenValue: undefined,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// We're disabling lint to trigger computed prop execution for this test.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { activeTokenValue } = wrapper.vm;
|
||||
|
||||
expect(wrapper.vm.fnActiveTokenValue).toHaveBeenCalledWith(
|
||||
mockLabels,
|
||||
`"${mockRegularLabel.title.toLowerCase()}"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('watch', () => {
|
||||
describe('tokenActive', () => {
|
||||
let wrapperWithTokenActive;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapperWithTokenActive = createComponent({
|
||||
props: {
|
||||
...mockProps,
|
||||
tokenActive: true,
|
||||
tokenValue: { data: `"${mockRegularLabel.title}"` },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapperWithTokenActive.destroy();
|
||||
});
|
||||
|
||||
it('emits `fetch-token-values` event on the component when value of this prop is changed to false and `tokenValues` array is empty', async () => {
|
||||
wrapperWithTokenActive.setProps({
|
||||
tokenActive: false,
|
||||
});
|
||||
|
||||
await wrapperWithTokenActive.vm.$nextTick();
|
||||
|
||||
expect(wrapperWithTokenActive.emitted('fetch-token-values')).toBeTruthy();
|
||||
expect(wrapperWithTokenActive.emitted('fetch-token-values')).toEqual([
|
||||
[`"${mockRegularLabel.title}"`],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('handleTokenValueSelected', () => {
|
||||
it('calls `setTokenValueToRecentlyUsed` when `recentTokenValuesStorageKey` is defined', () => {
|
||||
const mockTokenValue = {
|
||||
id: 1,
|
||||
title: 'Foo',
|
||||
};
|
||||
|
||||
wrapper.vm.handleTokenValueSelected(mockTokenValue);
|
||||
|
||||
expect(setTokenValueToRecentlyUsed).toHaveBeenCalledWith(mockStorageKey, mockTokenValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders gl-filtered-search-token component', () => {
|
||||
const wrapperWithNoStubs = createComponent({
|
||||
stubs: {},
|
||||
});
|
||||
const glFilteredSearchToken = wrapperWithNoStubs.find(GlFilteredSearchToken);
|
||||
|
||||
expect(glFilteredSearchToken.exists()).toBe(true);
|
||||
expect(glFilteredSearchToken.props('config')).toBe(mockLabelToken);
|
||||
|
||||
wrapperWithNoStubs.destroy();
|
||||
});
|
||||
|
||||
it('renders `view-token` slot when present', () => {
|
||||
expect(wrapper.find('.js-view-token').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders `view` slot when present', () => {
|
||||
expect(wrapper.find('.js-view').exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
let wrapperWithNoStubs;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapperWithNoStubs = createComponent({
|
||||
stubs: { Portal: true },
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapperWithNoStubs.destroy();
|
||||
});
|
||||
|
||||
it('emits `fetch-token-values` event on component after a delay when component emits `input` event', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: 'foo' });
|
||||
await wrapperWithNoStubs.vm.$nextTick();
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(wrapperWithNoStubs.emitted('fetch-token-values')).toBeTruthy();
|
||||
expect(wrapperWithNoStubs.emitted('fetch-token-values')[1]).toEqual(['foo']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -289,4 +289,38 @@ RSpec.describe CommitsHelper do
|
|||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "#commit_partial_cache_key" do
|
||||
subject { helper.commit_partial_cache_key(commit, ref: ref, merge_request: merge_request, request: request) }
|
||||
|
||||
let(:commit) { create(:commit).present(current_user: user) }
|
||||
let(:commit_status) { create(:commit_status) }
|
||||
let(:user) { create(:user) }
|
||||
let(:ref) { "master" }
|
||||
let(:merge_request) { nil }
|
||||
let(:request) { double(xhr?: true) }
|
||||
let(:current_path) { "test" }
|
||||
|
||||
before do
|
||||
expect(commit).to receive(:status_for).with(ref).and_return(commit_status)
|
||||
assign(:path, current_path)
|
||||
end
|
||||
|
||||
it { is_expected.to be_an(Array) }
|
||||
it { is_expected.to include(commit) }
|
||||
it { is_expected.to include(commit.author) }
|
||||
it { is_expected.to include(ref) }
|
||||
|
||||
it do
|
||||
is_expected.to include(
|
||||
{
|
||||
merge_request: merge_request,
|
||||
pipeline_status: Digest::SHA1.hexdigest(commit_status.to_s),
|
||||
xhr: true,
|
||||
controller: "commits",
|
||||
path: current_path
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
79
yarn.lock
79
yarn.lock
|
|
@ -867,10 +867,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.7.tgz#1ee6f838cc4410a1d797770934df91d90df8179e"
|
||||
integrity sha512-c6ySRK/Ma7lxwpIVbSAF3P+xiTLrNTGTLRx4/pHK111AdFxwgUwrYF6aVZFXvmG65jHOJHoa0eQQ21RW6rm0Rg==
|
||||
|
||||
"@gitlab/eslint-plugin@8.3.0":
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-8.3.0.tgz#156a375c6ab9e578ba39080932bca27006413486"
|
||||
integrity sha512-AuJ6ddKVbfjVUd9DLaNLhpflThZKULWatpUuI+0RhcqyRTmcb1KL5YPxxKDlE1K+faeefgiWaGB+vSNmyNNPQQ==
|
||||
"@gitlab/eslint-plugin@8.4.0":
|
||||
version "8.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-8.4.0.tgz#094fa4d41676a71146f82e1b19257a7ceabefd88"
|
||||
integrity sha512-VE/c1yIMrj2igJWAALQtAKpnXL8fN5wJ1uKteZfi8xYbWouoUK6hizXSPrrEUWiM2FqcBI4Igcpz2JlJzDlAnA==
|
||||
dependencies:
|
||||
babel-eslint "^10.0.3"
|
||||
eslint-config-airbnb-base "^14.2.1"
|
||||
|
|
@ -881,6 +881,7 @@
|
|||
eslint-plugin-jest "^23.8.2"
|
||||
eslint-plugin-promise "^4.2.1"
|
||||
eslint-plugin-vue "^7.5.0"
|
||||
lodash "4.17.20"
|
||||
vue-eslint-parser "^7.0.0"
|
||||
|
||||
"@gitlab/favicon-overlay@2.0.0":
|
||||
|
|
@ -4142,10 +4143,10 @@ debug@3.1.0, debug@~3.1.0:
|
|||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.1.0, debug@^3.1.1, debug@^3.2.6:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
debug@^3.1.0, debug@^3.1.1, debug@^3.2.6, debug@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
|
|
@ -4817,20 +4818,21 @@ eslint-import-resolver-node@^0.3.4:
|
|||
debug "^2.6.9"
|
||||
resolve "^1.13.1"
|
||||
|
||||
eslint-import-resolver-webpack@0.13.0:
|
||||
version "0.13.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.0.tgz#5cb19cf4b6996c8a2514aeb10f909e2c70488dc3"
|
||||
integrity sha512-hZWGcmjaJZK/WSCYGI/y4+FMGQZT+cwW/1E/P4rDwFj2PbanlQHISViw4ccDJ+2wxAqjgwBfxwy3seABbVKDEw==
|
||||
eslint-import-resolver-webpack@0.13.1:
|
||||
version "0.13.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.1.tgz#6d2fb928091daf2da46efa1e568055555b2de902"
|
||||
integrity sha512-O/8mG6AHmaKYSMb4lWxiXPpaARxOJ4rMQEHJ8vTgjS1MXooJA3KPgBPPAdOPoV17v5ML5120qod5FBLM+DtgEw==
|
||||
dependencies:
|
||||
array-find "^1.0.0"
|
||||
debug "^2.6.9"
|
||||
debug "^3.2.7"
|
||||
enhanced-resolve "^0.9.1"
|
||||
find-root "^1.1.0"
|
||||
has "^1.0.3"
|
||||
interpret "^1.2.0"
|
||||
lodash "^4.17.15"
|
||||
node-libs-browser "^1.0.0 || ^2.0.0"
|
||||
resolve "^1.13.1"
|
||||
interpret "^1.4.0"
|
||||
is-core-module "^2.4.0"
|
||||
is-regex "^1.1.3"
|
||||
lodash "^4.17.21"
|
||||
resolve "^1.20.0"
|
||||
semver "^5.7.1"
|
||||
|
||||
eslint-module-utils@^2.6.0:
|
||||
|
|
@ -5896,10 +5898,10 @@ has-flag@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
has-symbols@^1.0.0, has-symbols@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
|
||||
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
|
||||
has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
|
||||
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
|
||||
|
||||
has-value@^0.3.1:
|
||||
version "0.3.1"
|
||||
|
|
@ -6311,7 +6313,7 @@ internal-ip@^4.3.0:
|
|||
default-gateway "^4.2.0"
|
||||
ipaddr.js "^1.9.0"
|
||||
|
||||
interpret@^1.2.0, interpret@^1.4.0:
|
||||
interpret@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
|
||||
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
|
||||
|
|
@ -6409,6 +6411,13 @@ is-ci@^2.0.0:
|
|||
dependencies:
|
||||
ci-info "^2.0.0"
|
||||
|
||||
is-core-module@^2.2.0, is-core-module@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1"
|
||||
integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==
|
||||
dependencies:
|
||||
has "^1.0.3"
|
||||
|
||||
is-data-descriptor@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
|
||||
|
|
@ -6581,13 +6590,13 @@ is-potential-custom-element-name@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
|
||||
integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
|
||||
|
||||
is-regex@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251"
|
||||
integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
|
||||
is-regex@^1.1.1, is-regex@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
|
||||
integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==
|
||||
dependencies:
|
||||
call-bind "^1.0.2"
|
||||
has-symbols "^1.0.1"
|
||||
has-symbols "^1.0.2"
|
||||
|
||||
is-regexp@^2.0.0:
|
||||
version "2.1.0"
|
||||
|
|
@ -7866,6 +7875,11 @@ lodash.values@^4.3.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347"
|
||||
integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c=
|
||||
|
||||
lodash@4.17.20:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
|
||||
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
|
|
@ -8592,7 +8606,7 @@ node-int64@^0.4.0:
|
|||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
|
||||
|
||||
"node-libs-browser@^1.0.0 || ^2.0.0", node-libs-browser@^2.2.1:
|
||||
node-libs-browser@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
|
||||
integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==
|
||||
|
|
@ -10170,11 +10184,12 @@ resolve-url@^0.2.1:
|
|||
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
|
||||
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
|
||||
|
||||
resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0:
|
||||
version "1.17.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
|
||||
integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
|
||||
resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.20.0:
|
||||
version "1.20.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
||||
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
||||
dependencies:
|
||||
is-core-module "^2.2.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
responselike@^1.0.2:
|
||||
|
|
|
|||
Loading…
Reference in New Issue