Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-06-30 12:08:57 +00:00
parent e860bae967
commit 1e254d9f5a
70 changed files with 592 additions and 221 deletions

View File

@ -16,6 +16,7 @@ export const getMergeRequestsForBranch = (
.getProjectMergeRequests(`${projectId}`, {
source_branch: branchId,
source_project_id: state.projects[projectId].id,
state: 'opened',
order_by: 'created_at',
per_page: 1,
})

View File

@ -353,7 +353,10 @@ export default {
</gl-deprecated-button>
</div>
<div v-if="externalDashboardUrl.length" class="mb-2 mr-2 d-flex d-sm-block">
<div
v-if="externalDashboardUrl && externalDashboardUrl.length"
class="mb-2 mr-2 d-flex d-sm-block"
>
<gl-deprecated-button
class="flex-grow-1 js-external-dashboard-link"
variant="primary"

View File

@ -208,6 +208,14 @@ export const annotationsSymbolIcon = 'path://m5 229 5 8h-10z';
*/
export const DEFAULT_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml';
/**
* GitLab provide metrics dashboards that are available to a user once
* the Prometheus managed app has been installed, without any extra setup
* required. These "out of the box" dashboards are defined under the
* `config/prometheus` path.
*/
export const OUT_OF_THE_BOX_DASHBOARDS_PATH_PREFIX = 'config/prometheus/';
export const OPERATORS = {
greaterThan: '>',
equalTo: '==',

View File

@ -1,9 +1,9 @@
import Vue from 'vue';
import { GlToast } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
import { createStore } from './stores';
import createRouter from './router';
import { stateAndPropsFromDataset } from './utils';
Vue.use(GlToast);
@ -12,37 +12,10 @@ export default (props = {}) => {
if (el && el.dataset) {
const [currentDashboard] = getParameterValues('dashboard');
const { metricsDashboardBasePath, ...dataset } = el.dataset;
const {
deploymentsEndpoint,
dashboardEndpoint,
dashboardsEndpoint,
projectPath,
logsPath,
currentEnvironmentName,
dashboardTimezone,
metricsDashboardBasePath,
customDashboardBasePath,
...dataProps
} = el.dataset;
const store = createStore({
currentDashboard,
deploymentsEndpoint,
dashboardEndpoint,
dashboardsEndpoint,
dashboardTimezone,
projectPath,
logsPath,
currentEnvironmentName,
customDashboardBasePath,
});
// HTML attributes are always strings, parse other types.
dataProps.hasMetrics = parseBoolean(dataProps.hasMetrics);
dataProps.customMetricsAvailable = parseBoolean(dataProps.customMetricsAvailable);
dataProps.prometheusAlertsAvailable = parseBoolean(dataProps.prometheusAlertsAvailable);
const { initState, dataProps } = stateAndPropsFromDataset({ currentDashboard, ...dataset });
const store = createStore(initState);
const router = createRouter(metricsDashboardBasePath);
// eslint-disable-next-line no-new

View File

@ -6,7 +6,7 @@ import { mergeURLVariables, parseTemplatingVariables } from './variable_mapping'
import { DATETIME_RANGE_TYPES } from '~/lib/utils/constants';
import { timeRangeToParams, getRangeType } from '~/lib/utils/datetime_range';
import { isSafeURL, mergeUrlParams } from '~/lib/utils/url_utility';
import { NOT_IN_DB_PREFIX, linkTypes, DEFAULT_DASHBOARD_PATH } from '../constants';
import { NOT_IN_DB_PREFIX, linkTypes, OUT_OF_THE_BOX_DASHBOARDS_PATH_PREFIX } from '../constants';
export const gqClient = createGqClient(
{},
@ -479,7 +479,7 @@ export const normalizeCustomDashboardPath = (dashboard, dashboardPrefix = '') =>
dashboardPath = '';
} else if (
currDashboard.startsWith(dashboardPrefix) ||
currDashboard.startsWith(DEFAULT_DASHBOARD_PATH)
currDashboard.startsWith(OUT_OF_THE_BOX_DASHBOARDS_PATH_PREFIX)
) {
dashboardPath = currDashboard;
}

View File

@ -5,6 +5,7 @@ import {
removeParams,
updateHistory,
} from '~/lib/utils/url_utility';
import { parseBoolean } from '~/lib/utils/common_utils';
import {
timeRangeParamNames,
timeRangeFromParams,
@ -12,6 +13,46 @@ import {
} from '~/lib/utils/datetime_range';
import { VARIABLE_PREFIX } from './constants';
/**
* Extracts the initial state and props from HTML dataset
* and places them in separate objects to setup bundle.
* @param {*} dataset
*/
export const stateAndPropsFromDataset = (dataset = {}) => {
const {
currentDashboard,
deploymentsEndpoint,
dashboardEndpoint,
dashboardsEndpoint,
dashboardTimezone,
projectPath,
logsPath,
currentEnvironmentName,
customDashboardBasePath,
...dataProps
} = dataset;
// HTML attributes are always strings, parse other types.
dataProps.hasMetrics = parseBoolean(dataProps.hasMetrics);
dataProps.customMetricsAvailable = parseBoolean(dataProps.customMetricsAvailable);
dataProps.prometheusAlertsAvailable = parseBoolean(dataProps.prometheusAlertsAvailable);
return {
initState: {
currentDashboard,
deploymentsEndpoint,
dashboardEndpoint,
dashboardsEndpoint,
dashboardTimezone,
projectPath,
logsPath,
currentEnvironmentName,
customDashboardBasePath,
},
dataProps,
};
};
/**
* List of non time range url parameters
* This will be removed once we add support for free text variables

View File

@ -67,6 +67,11 @@ export default {
required: false,
default: false,
},
requirementsAvailable: {
type: Boolean,
required: false,
default: false,
},
visibilityHelpPath: {
type: String,
required: false,
@ -131,6 +136,7 @@ export default {
snippetsAccessLevel: featureAccessLevel.EVERYONE,
pagesAccessLevel: featureAccessLevel.EVERYONE,
metricsDashboardAccessLevel: featureAccessLevel.PROJECT_MEMBERS,
requirementsAccessLevel: featureAccessLevel.EVERYONE,
containerRegistryEnabled: true,
lfsEnabled: true,
requestAccessEnabled: true,
@ -233,6 +239,10 @@ export default {
featureAccessLevel.PROJECT_MEMBERS,
this.metricsDashboardAccessLevel,
);
this.requirementsAccessLevel = Math.min(
featureAccessLevel.PROJECT_MEMBERS,
this.requirementsAccessLevel,
);
if (this.pagesAccessLevel === featureAccessLevel.EVERYONE) {
// When from Internal->Private narrow access for only members
this.pagesAccessLevel = featureAccessLevel.PROJECT_MEMBERS;
@ -256,6 +266,9 @@ export default {
this.pagesAccessLevel = featureAccessLevel.EVERYONE;
if (this.metricsDashboardAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
this.metricsDashboardAccessLevel = featureAccessLevel.EVERYONE;
if (this.requirementsAccessLevel === featureAccessLevel.PROJECT_MEMBERS)
this.requirementsAccessLevel = featureAccessLevel.EVERYONE;
this.highlightChanges();
}
},
@ -470,6 +483,18 @@ export default {
/>
</project-setting-row>
</div>
<project-setting-row
v-if="requirementsAvailable"
ref="requirements-settings"
:label="s__('ProjectSettings|Requirements')"
:help-text="s__('ProjectSettings|Requirements management system for this project')"
>
<project-feature-setting
v-model="requirementsAccessLevel"
:options="featureAccessLevelOptions"
name="project[project_feature_attributes][requirements_access_level]"
/>
</project-setting-row>
<project-setting-row
ref="wiki-settings"
:label="s__('ProjectSettings|Wiki')"

View File

@ -2,6 +2,7 @@ export default {
data() {
return {
packagesEnabled: false,
requirementsEnabled: false,
};
},
watch: {

View File

@ -356,6 +356,20 @@ class ProjectsController < Projects::ApplicationController
.merge(import_url_params)
end
def project_feature_attributes
%i[
builds_access_level
issues_access_level
forking_access_level
merge_requests_access_level
repository_access_level
snippets_access_level
wiki_access_level
pages_access_level
metrics_dashboard_access_level
]
end
def project_params_attributes
[
:allow_merge_on_skipped_pipeline,
@ -391,22 +405,10 @@ class ProjectsController < Projects::ApplicationController
:initialize_with_readme,
:autoclose_referenced_issues,
:suggestion_commit_message,
project_feature_attributes: %i[
builds_access_level
issues_access_level
forking_access_level
merge_requests_access_level
repository_access_level
snippets_access_level
wiki_access_level
pages_access_level
metrics_dashboard_access_level
],
project_setting_attributes: %i[
show_default_award_emojis
]
]
] + [project_feature_attributes: project_feature_attributes]
end
def project_params_create_attributes

View File

@ -27,7 +27,7 @@ module Types
description: 'Indicates if Large File Storage (LFS) is enabled for namespace'
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if users can request access to namespace'
field :storage_size_limit, GraphQL::INT_TYPE, null: true,
field :storage_size_limit, GraphQL::FLOAT_TYPE, null: true,
description: 'Total storage limit of the root namespace in bytes',
resolve: -> (obj, _args, _ctx) { Namespace::RootStorageSize.new(obj).limit }

View File

@ -14,9 +14,10 @@ module StorageHelper
counter_repositories: storage_counter(statistics.repository_size),
counter_wikis: storage_counter(statistics.wiki_size),
counter_build_artifacts: storage_counter(statistics.build_artifacts_size),
counter_lfs_objects: storage_counter(statistics.lfs_objects_size)
counter_lfs_objects: storage_counter(statistics.lfs_objects_size),
counter_snippets: storage_counter(statistics.snippets_size)
}
_("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}") % counters
_("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}") % counters
end
end

View File

@ -566,7 +566,7 @@ module Ci
next unless project.repository_exists?
project.repository.list_commits_by_ref_name(refs).then do |commits|
loader.call(ref, commits[ref])
commits.each { |key, commit| loader.call(key, commits[key]) }
end
end
end

View File

@ -37,7 +37,8 @@ module Featurable
class_methods do
def set_available_features(available_features = [])
@available_features = available_features
@available_features ||= []
@available_features += available_features
class_eval do
available_features.each do |feature|

View File

@ -88,3 +88,5 @@ module ProjectFeaturesCompatibility
project_feature.__send__(:write_attribute, field, value) # rubocop:disable GitlabSecurity/PublicSend
end
end
ProjectsHelper.prepend_if_ee('EE::ProjectFeaturesCompatibility')

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class CustomEmoji < ApplicationRecord
belongs_to :namespace, inverse_of: :custom_emoji
validate :valid_emoji_name
validates :namespace, presence: true
validates :name,
uniqueness: { scope: [:namespace_id, :name] },
presence: true,
length: { maximum: 36 },
format: { with: /\A\w+\z/ }
private
def valid_emoji_name
if Gitlab::Emoji.emoji_exists?(name)
errors.add(:name, _('%{name} is already being used for another emoji') % { name: self.name })
end
end
end

View File

@ -32,6 +32,7 @@ class Namespace < ApplicationRecord
belongs_to :parent, class_name: "Namespace"
has_many :children, class_name: "Namespace", foreign_key: :parent_id
has_many :custom_emoji, inverse_of: :namespace
has_one :chat_team, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :root_storage_statistics, class_name: 'Namespace::RootStorageStatistics'
has_one :aggregation_schedule, class_name: 'Namespace::AggregationSchedule'

View File

@ -79,8 +79,6 @@ module Projects
full_path
)
new_repository.create_repository
new_repository.replicate(raw_repository)
new_checksum = new_repository.checksum

View File

@ -13,8 +13,6 @@ module ResourceAccessTokens
return unless feature_enabled?
return error("User does not have permission to create #{resource_type} Access Token") unless has_permission_to_create?
# We skip authorization by default, since the user creating the bot is not an admin
# and project/group bot users are not created via sign-up
user = create_user
return error(user.errors.full_messages.to_sentence) unless user.persisted?
@ -49,6 +47,11 @@ module ResourceAccessTokens
end
def create_user
# Even project maintainers can create project access tokens, which in turn
# creates a bot user, and so it becomes necessary to have `skip_authorization: true`
# since someone like a project maintainer does not inherently have the ability
# to create a new user in the system.
Users::CreateService.new(current_user, default_user_params).execute(skip_authorization: true)
end
@ -57,7 +60,8 @@ module ResourceAccessTokens
name: params[:name] || "#{resource.name.to_s.humanize} bot",
email: generate_email,
username: generate_username,
user_type: "#{resource_type}_bot".to_sym
user_type: "#{resource_type}_bot".to_sym,
skip_confirmation: true # Bot users should always have their emails confirmed.
}
end

View File

@ -0,0 +1,4 @@
---
title: Only show open Merge Requests in Web IDE
merge_request: 35514
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Project bot users should always have their emails confirmed by default
merge_request: 35498
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add requirements visibility/access project settings
merge_request: 34420
author: Lee Tickett
type: added

View File

@ -0,0 +1,5 @@
---
title: Add custom emoji model and database table
merge_request: 24229
author: Rajendra Kadam
type: added

View File

@ -0,0 +1,5 @@
---
title: Use FLOAT_TYPE for storage limit
merge_request: 35559
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Use snapshot transfers for repository shard moves when possible
merge_request: 34113
author:
type: performance

View File

@ -11,6 +11,7 @@
#
ActiveSupport::Inflector.inflections do |inflect|
inflect.uncountable %w(
custom_emoji
award_emoji
container_repository_registry
design_registry

View File

@ -0,0 +1,29 @@
class CreateCustomEmojis < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
unless table_exists?(:custom_emoji)
create_table :custom_emoji do |t|
t.references :namespace, index: false, null: false, foreign_key: { on_delete: :cascade }
t.datetime_with_timezone :created_at, null: false
t.datetime_with_timezone :updated_at, null: false
t.text :name, null: false
t.text :file, null: false
end
end
unless index_exists?(:custom_emoji, [:namespace_id, :name], unique: true)
add_index :custom_emoji, [:namespace_id, :name], unique: true
end
add_text_limit(:custom_emoji, :name, 36)
add_text_limit(:custom_emoji, :file, 255)
end
def down
drop_table :custom_emoji
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class AddRequirementsAccessLevelToProjectFeatures < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
add_column :project_features, :requirements_access_level, :integer, default: 20, null: false
end
end
def down
with_lock_retries do
remove_column :project_features, :requirements_access_level, :integer
end
end
end

View File

@ -10818,6 +10818,26 @@ CREATE SEQUENCE public.conversational_development_index_metrics_id_seq
ALTER SEQUENCE public.conversational_development_index_metrics_id_seq OWNED BY public.conversational_development_index_metrics.id;
CREATE TABLE public.custom_emoji (
id bigint NOT NULL,
namespace_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
name text NOT NULL,
file text NOT NULL,
CONSTRAINT check_8c586dd507 CHECK ((char_length(name) <= 36)),
CONSTRAINT check_dd5d60f1fb CHECK ((char_length(file) <= 255))
);
CREATE SEQUENCE public.custom_emoji_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.custom_emoji_id_seq OWNED BY public.custom_emoji.id;
CREATE TABLE public.dependency_proxy_blobs (
id integer NOT NULL,
group_id integer NOT NULL,
@ -13956,7 +13976,8 @@ CREATE TABLE public.project_features (
repository_access_level integer DEFAULT 20 NOT NULL,
pages_access_level integer NOT NULL,
forking_access_level integer,
metrics_dashboard_access_level integer
metrics_dashboard_access_level integer,
requirements_access_level integer DEFAULT 20 NOT NULL
);
CREATE SEQUENCE public.project_features_id_seq
@ -16393,6 +16414,8 @@ ALTER TABLE ONLY public.container_repositories ALTER COLUMN id SET DEFAULT nextv
ALTER TABLE ONLY public.conversational_development_index_metrics ALTER COLUMN id SET DEFAULT nextval('public.conversational_development_index_metrics_id_seq'::regclass);
ALTER TABLE ONLY public.custom_emoji ALTER COLUMN id SET DEFAULT nextval('public.custom_emoji_id_seq'::regclass);
ALTER TABLE ONLY public.dependency_proxy_blobs ALTER COLUMN id SET DEFAULT nextval('public.dependency_proxy_blobs_id_seq'::regclass);
ALTER TABLE ONLY public.dependency_proxy_group_settings ALTER COLUMN id SET DEFAULT nextval('public.dependency_proxy_group_settings_id_seq'::regclass);
@ -17348,6 +17371,9 @@ ALTER TABLE ONLY public.container_repositories
ALTER TABLE ONLY public.conversational_development_index_metrics
ADD CONSTRAINT conversational_development_index_metrics_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.custom_emoji
ADD CONSTRAINT custom_emoji_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.dependency_proxy_blobs
ADD CONSTRAINT dependency_proxy_blobs_pkey PRIMARY KEY (id);
@ -18814,6 +18840,8 @@ CREATE UNIQUE INDEX index_container_repositories_on_project_id_and_name ON publi
CREATE INDEX index_container_repository_on_name_trigram ON public.container_repositories USING gin (name public.gin_trgm_ops);
CREATE UNIQUE INDEX index_custom_emoji_on_namespace_id_and_name ON public.custom_emoji USING btree (namespace_id, name);
CREATE UNIQUE INDEX index_daily_build_group_report_results_unique_columns ON public.ci_daily_build_group_report_results USING btree (project_id, ref_path, date, group_name);
CREATE UNIQUE INDEX index_daily_report_results_unique_columns ON public.ci_daily_report_results USING btree (project_id, ref_path, param_type, date, title);
@ -21778,6 +21806,9 @@ ALTER TABLE ONLY public.project_custom_attributes
ALTER TABLE ONLY public.slack_integrations
ADD CONSTRAINT fk_rails_73db19721a FOREIGN KEY (service_id) REFERENCES public.services(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.custom_emoji
ADD CONSTRAINT fk_rails_745925b412 FOREIGN KEY (namespace_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.merge_request_context_commit_diff_files
ADD CONSTRAINT fk_rails_74a00a1787 FOREIGN KEY (merge_request_context_commit_id) REFERENCES public.merge_request_context_commits(id) ON DELETE CASCADE;
@ -23073,6 +23104,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200227164113
20200227165129
20200228160542
20200229171700
20200302142052
20200302152516
20200303055348
@ -23427,6 +23459,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200615121217
20200615123055
20200615193524
20200615203153
20200615232735
20200615234047
20200616145031

View File

@ -19,11 +19,20 @@ for steps to test filesystem performance.
## Known kernel version incompatibilities
The following kernel versions should NOT be used with GitLab:
RedHat Enterprise Linux (RHEL) and CentOS v7.7 and v7.8 ship with kernel
version `3.10.0-1127`, which [contains a
bug](https://bugzilla.redhat.com/show_bug.cgi?id=1783554) that causes
[uploads to fail to copy over NFS](https://gitlab.com/gitlab-org/gitlab/-/issues/218999). The
following GitLab versions include a fix to work properly with that
kernel version:
|Distribution |Kernel Versions|Affected GitLab versions|Details|
|-------------|---------------|------------------------|-------|
|RHEL/CentOS|`3.10.0-1127*`|All prior to GitLab 13.1|[Uploads fail to copy over NFS](https://gitlab.com/gitlab-org/gitlab/-/issues/218999)|
1. [12.10.12](https://about.gitlab.com/releases/2020/06/25/gitlab-12-10-12-released/)
1. [13.0.7](https://about.gitlab.com/releases/2020/06/25/gitlab-13-0-7-released/)
1. [13.1.1](https://about.gitlab.com/releases/2020/06/24/gitlab-13-1-1-released/)
1. 13.2 and up
If you are using that kernel version, be sure to upgrade GitLab to avoid
errors.
## NFS Server features

View File

@ -5236,7 +5236,7 @@ type Group {
"""
Total storage limit of the root namespace in bytes
"""
storageSizeLimit: Int
storageSizeLimit: Float
"""
The permission level required to create subgroups within the group
@ -8081,7 +8081,7 @@ type Namespace {
"""
Total storage limit of the root namespace in bytes
"""
storageSizeLimit: Int
storageSizeLimit: Float
"""
Visibility of the namespace

View File

@ -14407,7 +14407,7 @@
],
"type": {
"kind": "SCALAR",
"name": "Int",
"name": "Float",
"ofType": null
},
"isDeprecated": false,
@ -24094,7 +24094,7 @@
],
"type": {
"kind": "SCALAR",
"name": "Int",
"name": "Float",
"ofType": null
},
"isDeprecated": false,

View File

@ -804,7 +804,7 @@ Autogenerated return type of EpicTreeReorder
| `requireTwoFactorAuthentication` | Boolean | Indicates if all users in this group are required to set up two-factor authentication |
| `rootStorageStatistics` | RootStorageStatistics | Aggregated storage statistics of the namespace. Only available for root namespaces |
| `shareWithGroupLock` | Boolean | Indicates if sharing a project with another group within this group is prevented |
| `storageSizeLimit` | Int | Total storage limit of the root namespace in bytes |
| `storageSizeLimit` | Float | Total storage limit of the root namespace in bytes |
| `subgroupCreationLevel` | String | The permission level required to create subgroups within the group |
| `twoFactorGracePeriod` | Int | Time before two-factor authentication is enforced |
| `userPermissions` | GroupPermissions! | Permissions for the current user on the resource |
@ -1239,7 +1239,7 @@ Contains statistics about a milestone
| `path` | String! | Path of the namespace |
| `requestAccessEnabled` | Boolean | Indicates if users can request access to namespace |
| `rootStorageStatistics` | RootStorageStatistics | Aggregated storage statistics of the namespace. Only available for root namespaces |
| `storageSizeLimit` | Int | Total storage limit of the root namespace in bytes |
| `storageSizeLimit` | Float | Total storage limit of the root namespace in bytes |
| `visibility` | String | Visibility of the namespace |
## Note

View File

@ -1048,6 +1048,7 @@ POST /projects
| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `pages_access_level` | string | no | One of `disabled`, `private`, `enabled` or `public` |
| `requirements_access_level` | string | no | One of `disabled`, `private`, `enabled` or `public` |
| `emails_disabled` | boolean | no | Disable email notifications |
| `show_default_award_emojis` | boolean | no | Show default award emojis |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
@ -1119,6 +1120,7 @@ POST /projects/user/:user_id
| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `pages_access_level` | string | no | One of `disabled`, `private`, `enabled` or `public` |
| `requirements_access_level` | string | no | One of `disabled`, `private`, `enabled` or `public` |
| `emails_disabled` | boolean | no | Disable email notifications |
| `show_default_award_emojis` | boolean | no | Show default award emojis |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
@ -1189,6 +1191,7 @@ PUT /projects/:id
| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `pages_access_level` | string | no | One of `disabled`, `private`, `enabled` or `public` |
| `requirements_access_level` | string | no | One of `disabled`, `private`, `enabled` or `public` |
| `emails_disabled` | boolean | no | Disable email notifications |
| `show_default_award_emojis` | boolean | no | Show default award emojis |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |

View File

@ -91,7 +91,7 @@ You can add a command to your `.gitlab-ci.yml` file to
| `CI_PAGES_URL` | 11.8 | all | URL to GitLab Pages-built pages. Always belongs to a subdomain of `CI_PAGES_DOMAIN`. |
| `CI_PIPELINE_ID` | 8.10 | all | The unique ID of the current pipeline that GitLab CI/CD uses internally |
| `CI_PIPELINE_IID` | 11.0 | all | The unique ID of the current pipeline scoped to project |
| `CI_PIPELINE_SOURCE` | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, `pipeline`, `parent_pipeline`, `external`, `chat`, `merge_request_event`, and `external_pull_request_event`. For pipelines created before GitLab 9.5, this will show as `unknown` |
| `CI_PIPELINE_SOURCE` | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, `external`, `pipeline` (renamed to `cross_project_pipeline` since 13.0), `chat`, `webide`, `merge_request_event`, `external_pull_request_event`, and `parent_pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
| `CI_PIPELINE_TRIGGERED` | all | all | The flag to indicate that job was [triggered](../triggers/README.md) |
| `CI_PIPELINE_URL` | 11.1 | 0.5 | Pipeline details URL |
| `CI_PROJECT_DIR` | all | all | The full path where the repository is cloned and where the job is run. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see [Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) for GitLab Runner. |

View File

@ -813,6 +813,40 @@ file which is used by the `spec/fast_spec_helper.rb` file. See
[Fast unit tests](#fast-unit-tests) for more details about the
`spec/fast_spec_helper.rb` file.
### Test environment logging
Services for the test environment are automatically configured and started when
tests are run, including Gitaly, Workhorse, Elasticsearch, and Capybara. When run in CI, or
if the service needs to be installed, the test environment will log information
about set-up time, producing log messages like the following:
```plaintext
==> Setting up Gitaly...
Gitaly set up in 31.459649 seconds...
==> Setting up GitLab Workhorse...
GitLab Workhorse set up in 29.695619 seconds...
fatal: update refs/heads/diff-files-symlink-to-image: invalid <newvalue>: 8cfca84
From https://gitlab.com/gitlab-org/gitlab-test
* [new branch] diff-files-image-to-symlink -> origin/diff-files-image-to-symlink
* [new branch] diff-files-symlink-to-image -> origin/diff-files-symlink-to-image
* [new branch] diff-files-symlink-to-text -> origin/diff-files-symlink-to-text
* [new branch] diff-files-text-to-symlink -> origin/diff-files-text-to-symlink
b80faa8..40232f7 snippet/multiple-files -> origin/snippet/multiple-files
* [new branch] testing/branch-with-#-hash -> origin/testing/branch-with-#-hash
==> Setting up GitLab Elasticsearch Indexer...
GitLab Elasticsearch Indexer set up in 26.514623 seconds...
```
This information is omitted when running locally and when no action needs
to be performed. If you would always like to see these messages, set the
following environment variable:
```shell
GITLAB_TESTING_LOG_LEVEL=debug
```
---
[Return to Testing documentation](index.md)

View File

@ -271,6 +271,22 @@ you defined.
| `week` | 4 |
| `month` | 12 |
#### `query.period_field`
Define the timestamp field used to group "issuables".
Supported values are:
- `created_at` (default): Group data using the `created_at` field.
- `closed_at`: Group data using the `closed_at` field (for issues only).
- `merged_at`: Group data using the `merged_at` field (for merge requests only).
The `period_field` is automatically set to:
- `closed_at` if `query.issuable_state` is `closed`
- `merged_at` if `query.issuable_state` is `merged`
- `created_at` otherwise
### `projects`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10904) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.4.

View File

@ -62,6 +62,7 @@ Use the switches to enable or disable the following features:
| **Snippets** | ✓ | Enables [sharing of code and text](../../snippets.md) |
| **Pages** | ✓ | Allows you to [publish static websites](../pages/) |
| **Metrics Dashboard** | ✓ | Control access to [metrics dashboard](../integrations/prometheus.md)
| **Requirements** | ✓ | Control access to [Requirements Management](../requirements/index.md)
Some features depend on others:

View File

@ -22,7 +22,6 @@ now search for code within other teams that can help your own project.
GitLab leverages the search capabilities of [Elasticsearch](https://www.elastic.co/elasticsearch/) and enables it when
searching in:
- GitLab application
- Projects
- Repositories
- Commits

View File

@ -34,26 +34,13 @@ class Feature
def persisted_names
return [] unless Gitlab::Database.exists?
if Gitlab::Utils.to_boolean(ENV['FF_LEGACY_PERSISTED_NAMES'])
# To be removed:
# This uses a legacy persisted names that are know to work (always)
Gitlab::SafeRequestStore[:flipper_persisted_names] ||=
begin
# We saw on GitLab.com, this database request was called 2300
# times/s. Let's cache it for a minute to avoid that load.
Gitlab::ProcessMemoryCache.cache_backend.fetch('flipper:persisted_names', expires_in: 1.minute) do
FlipperFeature.feature_names
end.to_set
end
else
# This loads names of all stored feature flags
# and returns a stable Set in the following order:
# - Memoized: using Gitlab::SafeRequestStore or @flipper
# - L1: using Process cache
# - L2: using Redis cache
# - DB: using a single SQL query
flipper.adapter.features
end
# This loads names of all stored feature flags
# and returns a stable Set in the following order:
# - Memoized: using Gitlab::SafeRequestStore or @flipper
# - L1: using Process cache
# - L2: using Redis cache
# - DB: using a single SQL query
flipper.adapter.features
end
def persisted_name?(feature_name)

View File

@ -44,6 +44,10 @@ module Gitlab
"<img class='emoji' title=':#{name}:' alt=':#{name}:' src='#{src}' height='20' width='20' align='absmiddle' />"
end
def emoji_exists?(name)
emojis.has_key?(name)
end
# CSS sprite fallback takes precedence over image fallback
def gl_emoji_tag(name, options = {})
emoji_name = emojis_aliases[name] || name

View File

@ -396,13 +396,13 @@ module Gitlab
def list_commits_by_ref_name(refs)
request = Gitaly::ListCommitsByRefNameRequest
.new(repository: @gitaly_repo, ref_names: refs)
.new(repository: @gitaly_repo, ref_names: refs.map { |ref| encode_binary(ref) })
response = GitalyClient.call(@repository.storage, :commit_service, :list_commits_by_ref_name, request, timeout: GitalyClient.medium_timeout)
commit_refs = response.flat_map do |message|
message.commit_refs.map do |commit_ref|
[commit_ref.ref_name, Gitlab::Git::Commit.new(@repository, commit_ref.commit)]
[encode_utf8(commit_ref.ref_name), Gitlab::Git::Commit.new(@repository, commit_ref.commit)]
end
end

View File

@ -29,6 +29,7 @@ module Quality
assignee_ids: Array(team.pluck(:id).sample(3)),
labels: labels.join(',')
}
params[:closed_at] = params[:created_at] + rand(35).days if params[:state] == 'closed'
issue = ::Issues::CreateService.new(project, team.sample, params).execute
if issue.persisted?

View File

@ -476,6 +476,9 @@ msgstr ""
msgid "%{name} found %{resultsString}"
msgstr ""
msgid "%{name} is already being used for another emoji"
msgstr ""
msgid "%{name} is scheduled for %{action}"
msgstr ""
@ -17782,6 +17785,12 @@ msgstr ""
msgid "ProjectSettings|Repository"
msgstr ""
msgid "ProjectSettings|Requirements"
msgstr ""
msgid "ProjectSettings|Requirements management system for this project"
msgstr ""
msgid "ProjectSettings|Share code pastes with others out of Git repository"
msgstr ""
@ -19222,7 +19231,7 @@ msgstr ""
msgid "Repository sync capacity"
msgstr ""
msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
msgstr ""
msgid "RepositorySettingsAccessLevel|Select"
@ -24728,6 +24737,9 @@ msgstr ""
msgid "UsageQuota|Repository"
msgstr ""
msgid "UsageQuota|Snippets"
msgstr ""
msgid "UsageQuota|Storage"
msgstr ""

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
FactoryBot.define do
factory :custom_emoji, class: 'CustomEmoji' do
sequence(:name) { |n| "custom_emoji#{n}" }
namespace
file { fixture_file_upload(Rails.root.join('spec/fixtures/dk.png')) }
end
end

View File

@ -181,6 +181,7 @@ FactoryBot.define do
transient do
create_templates { nil }
create_branch { nil }
end
after :create do |project, evaluator|
@ -206,6 +207,16 @@ FactoryBot.define do
message: 'test 2',
branch_name: 'master')
end
if evaluator.create_branch
project.repository.create_file(
project.creator,
'README.md',
"README on branch #{evaluator.create_branch}",
message: 'Add README.md',
branch_name: evaluator.create_branch)
end
end
end

View File

@ -15,7 +15,7 @@ RSpec.describe "Admin > Admin sees project statistics" do
let(:project) { create(:project, :repository) }
it "shows project statistics" do
expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes)")
expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes / Snippets: 0 Bytes)")
end
end

View File

@ -6,10 +6,11 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
include MetricsDashboardHelpers
let(:user) { create(:user) }
let(:project) { project_with_dashboard('.gitlab/dashboards/test.yml') }
let(:environment) { create(:environment, project: project) }
let(:params) { { environment: environment } }
let_it_be(:user) { create(:user) }
let_it_be(:namespace) { create(:namespace, name: 'monitoring' )}
let_it_be(:project) { project_with_dashboard_namespace('.gitlab/dashboards/test.yml', namespace: namespace) }
let_it_be(:environment) { create(:environment, id: 1, project: project) }
let_it_be(:params) { { environment: environment } }
before(:all) do
clean_frontend_fixtures('metrics_dashboard/')
@ -24,6 +25,7 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
project.add_maintainer(user)
allow(controller).to receive(:project).and_return(project)
allow(controller).to receive(:environment).and_return(environment)
allow(controller)
.to receive(:metrics_dashboard_params)
.and_return(params)
@ -35,7 +37,9 @@ RSpec.describe MetricsDashboard, '(JavaScript fixtures)', type: :controller do
it 'metrics_dashboard/environment_metrics_dashboard.json' do
routes.draw { get "metrics_dashboard" => "anonymous#metrics_dashboard" }
response = get :metrics_dashboard, format: :json
expect(response).to be_successful
end
end

View File

@ -55,6 +55,7 @@ describe('IDE store merge request actions', () => {
expect(service.getProjectMergeRequests).toHaveBeenCalledWith(TEST_PROJECT, {
source_branch: 'bar',
source_project_id: TEST_PROJECT_ID,
state: 'opened',
order_by: 'created_at',
per_page: 1,
});

View File

@ -4,6 +4,10 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<div
class="prometheus-graphs"
data-qa-selector="prometheus_graphs"
environmentstate="available"
metricsdashboardbasepath="/monitoring/monitor-project/-/environments/1/metrics"
metricsendpoint="/monitoring/monitor-project/-/environments/1/additional_metrics.json"
prometheusstatus=""
>
<div
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
@ -135,15 +139,15 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<!---->
<empty-state-stub
clusterspath="/path/to/clusters"
documentationpath="/path/to/docs"
emptygettingstartedsvgpath="/path/to/getting-started.svg"
emptyloadingsvgpath="/path/to/loading.svg"
emptynodatasmallsvgpath="/path/to/no-data-small.svg"
emptynodatasvgpath="/path/to/no-data.svg"
emptyunabletoconnectsvgpath="/path/to/unable-to-connect.svg"
clusterspath="/monitoring/monitor-project/-/clusters"
documentationpath="/help/administration/monitoring/prometheus/index.md"
emptygettingstartedsvgpath="/images/illustrations/monitoring/getting_started.svg"
emptyloadingsvgpath="/images/illustrations/monitoring/loading.svg"
emptynodatasmallsvgpath="/images/illustrations/chart-empty-state-small.svg"
emptynodatasvgpath="/images/illustrations/monitoring/no_data.svg"
emptyunabletoconnectsvgpath="/images/illustrations/monitoring/unable_to_connect.svg"
selectedstate="gettingStarted"
settingspath="/path/to/settings"
settingspath="/monitoring/monitor-project/-/services/prometheus/edit"
/>
</div>
`;

View File

@ -18,8 +18,8 @@ import {
singleStatMetricsResult,
graphDataPrometheusQueryRangeMultiTrack,
barMockData,
propsData,
} from '../mock_data';
import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data';
import { panelTypes } from '~/monitoring/constants';
@ -32,7 +32,6 @@ import MonitorColumnChart from '~/monitoring/components/charts/column.vue';
import MonitorBarChart from '~/monitoring/components/charts/bar.vue';
import MonitorStackedColumnChart from '~/monitoring/components/charts/stacked_column.vue';
import { graphData, graphDataEmpty } from '../fixture_data';
import { createStore, monitoringDashboard } from '~/monitoring/stores';
import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
@ -63,7 +62,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
settingsPath: propsData.settingsPath,
settingsPath: dashboardProps.settingsPath,
...props,
},
store,
@ -316,7 +315,7 @@ describe('Dashboard Panel', () => {
return wrapper.vm.$nextTick(() => {
expect(findEditCustomMetricLink().text()).toBe('Edit metrics');
expect(findEditCustomMetricLink().attributes('href')).toBe(propsData.settingsPath);
expect(findEditCustomMetricLink().attributes('href')).toBe(dashboardProps.settingsPath);
});
});
});
@ -433,7 +432,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
settingsPath: propsData.settingsPath,
settingsPath: dashboardProps.settingsPath,
graphData: {
y_label: 'metric',
...graphData,
@ -483,7 +482,7 @@ describe('Dashboard Panel', () => {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
settingsPath: propsData.settingsPath,
settingsPath: dashboardProps.settingsPath,
namespace: mockNamespace,
},
store,

View File

@ -29,8 +29,12 @@ import {
setupStoreWithVariable,
setupStoreWithLinks,
} from '../store_utils';
import { environmentData, dashboardGitResponse, propsData } from '../mock_data';
import { metricsDashboardViewModel, metricsDashboardPanelCount } from '../fixture_data';
import { environmentData, dashboardGitResponse } from '../mock_data';
import {
metricsDashboardViewModel,
metricsDashboardPanelCount,
dashboardProps,
} from '../fixture_data';
import createFlash from '~/flash';
jest.mock('~/flash');
@ -50,7 +54,7 @@ describe('Dashboard', () => {
const createShallowWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(Dashboard, {
propsData: { ...propsData, ...props },
propsData: { ...dashboardProps, ...props },
store,
stubs: {
DashboardHeader,
@ -61,7 +65,7 @@ describe('Dashboard', () => {
const createMountedWrapper = (props = {}, options = {}) => {
wrapper = mount(Dashboard, {
propsData: { ...propsData, ...props },
propsData: { ...dashboardProps, ...props },
store,
stubs: {
'graph-group': true,

View File

@ -5,7 +5,7 @@ import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
import { createStore } from '~/monitoring/stores';
import { setupAllDashboards } from '../store_utils';
import { propsData } from '../mock_data';
import { dashboardProps } from '../fixture_data';
jest.mock('~/lib/utils/url_utility');
@ -29,7 +29,7 @@ describe('Dashboard template', () => {
it('matches the default snapshot', () => {
wrapper = shallowMount(Dashboard, {
propsData: { ...propsData },
propsData: { ...dashboardProps },
store,
stubs: {
DashboardHeader,

View File

@ -9,7 +9,8 @@ import {
updateHistory,
} from '~/lib/utils/url_utility';
import axios from '~/lib/utils/axios_utils';
import { mockProjectDir, propsData } from '../mock_data';
import { mockProjectDir } from '../mock_data';
import { dashboardProps } from '../fixture_data';
import Dashboard from '~/monitoring/components/dashboard.vue';
import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
@ -26,7 +27,7 @@ describe('dashboard invalid url parameters', () => {
const createMountedWrapper = (props = { hasMetrics: true }, options = {}) => {
wrapper = mount(Dashboard, {
propsData: { ...propsData, ...props },
propsData: { ...dashboardProps, ...props },
store,
stubs: { 'graph-group': true, 'dashboard-panel': true, 'dashboard-header': DashboardHeader },
...options,

View File

@ -1,5 +1,8 @@
import { stateAndPropsFromDataset } from '~/monitoring/utils';
import { mapToDashboardViewModel } from '~/monitoring/stores/utils';
import { metricStates } from '~/monitoring/constants';
import { convertObjectProps } from '~/lib/utils/common_utils';
import { convertToCamelCase } from '~/lib/utils/text_utility';
import { metricsResult } from './mock_data';
@ -8,6 +11,19 @@ export const metricsDashboardResponse = getJSONFixture(
'metrics_dashboard/environment_metrics_dashboard.json',
);
export const metricsDashboardPayload = metricsDashboardResponse.dashboard;
const datasetState = stateAndPropsFromDataset(
// It's preferable to have props in snake_case, this will be addressed at:
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/33574
convertObjectProps(
// Some props use kebab-case, convert to snake_case first
key => convertToCamelCase(key.replace(/-/g, '_')),
metricsDashboardResponse.metrics_data,
),
);
export const dashboardProps = datasetState.dataProps;
export const metricsDashboardViewModel = mapToDashboardViewModel(metricsDashboardPayload);
export const metricsDashboardPanelCount = 22;

View File

@ -877,6 +877,8 @@ describe('normalizeCustomDashboardPath', () => {
${['.gitlab/dashboards/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/links.yml'}
${['.gitlab/dashboards/dir1/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/links.yml'}
${['.gitlab/dashboards/dir1/dir2/links.yml', '.gitlab/dashboards']} | ${'.gitlab/dashboards/dir1/dir2/links.yml'}
${['config/prometheus/pod_metrics.yml', '.gitlab/dashboards']} | ${'config/prometheus/pod_metrics.yml'}
${['config/prometheus/pod_metrics.yml']} | ${'config/prometheus/pod_metrics.yml'}
`(`normalizeCustomDashboardPath returns $expected for $input`, ({ input, expected }) => {
expect(normalizeCustomDashboardPath(...input)).toEqual(expected);
});

View File

@ -30,10 +30,11 @@ RSpec.describe StorageHelper do
repository_size: 10.kilobytes,
wiki_size: 10.bytes,
lfs_objects_size: 20.gigabytes,
build_artifacts_size: 30.megabytes))
build_artifacts_size: 30.megabytes,
snippets_size: 40.megabytes))
end
let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB' }
let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB / Snippets: 40 MB' }
it 'works on ProjectStatistics' do
expect(helper.storage_counters_details(project.statistics)).to eq(message)

View File

@ -11,8 +11,8 @@ import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
import { mockApiEndpoint, propsData } from '../mock_data';
import { metricsDashboardPayload } from '../fixture_data';
import { mockApiEndpoint } from '../mock_data';
import { metricsDashboardPayload, dashboardProps } from '../fixture_data';
import { setupStoreWithData } from '../store_utils';
const localVue = createLocalVue();
@ -56,7 +56,7 @@ describe('Dashboard', () => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
...dashboardProps,
hasMetrics: true,
showPanels: true,
},

View File

@ -21,66 +21,29 @@ RSpec.describe Feature, stub_feature_flags: false do
end
describe '.persisted_names' do
context 'when FF_LEGACY_PERSISTED_NAMES=false' do
before do
stub_env('FF_LEGACY_PERSISTED_NAMES', 'false')
end
it 'returns the names of the persisted features' do
Feature.enable('foo')
it 'returns the names of the persisted features' do
Feature.enable('foo')
expect(described_class.persisted_names).to contain_exactly('foo')
end
it 'returns an empty Array when no features are presisted' do
expect(described_class.persisted_names).to be_empty
end
it 'caches the feature names when request store is active',
:request_store, :use_clean_rails_memory_store_caching do
Feature.enable('foo')
expect(Gitlab::ProcessMemoryCache.cache_backend)
.to receive(:fetch)
.once
.with('flipper/v1/features', expires_in: 1.minute)
.and_call_original
2.times do
expect(described_class.persisted_names).to contain_exactly('foo')
end
end
expect(described_class.persisted_names).to contain_exactly('foo')
end
context 'when FF_LEGACY_PERSISTED_NAMES=true' do
before do
stub_env('FF_LEGACY_PERSISTED_NAMES', 'true')
end
it 'returns an empty Array when no features are presisted' do
expect(described_class.persisted_names).to be_empty
end
it 'returns the names of the persisted features' do
Feature.enable('foo')
it 'caches the feature names when request store is active',
:request_store, :use_clean_rails_memory_store_caching do
Feature.enable('foo')
expect(Gitlab::ProcessMemoryCache.cache_backend)
.to receive(:fetch)
.once
.with('flipper/v1/features', expires_in: 1.minute)
.and_call_original
2.times do
expect(described_class.persisted_names).to contain_exactly('foo')
end
it 'returns an empty Array when no features are presisted' do
expect(described_class.persisted_names).to be_empty
end
it 'caches the feature names when request store is active',
:request_store, :use_clean_rails_memory_store_caching do
Feature.enable('foo')
expect(Gitlab::ProcessMemoryCache.cache_backend)
.to receive(:fetch)
.once
.with('flipper:persisted_names', expires_in: 1.minute)
.and_call_original
2.times do
expect(described_class.persisted_names).to contain_exactly('foo')
end
end
end
it 'fetches all flags once in a single query', :request_store do

View File

@ -95,6 +95,20 @@ RSpec.describe Gitlab::Emoji do
end
end
describe '.emoji_exists?' do
it 'returns true if the name exists' do
emoji_exists = described_class.emoji_exists?('100')
expect(emoji_exists).to be_truthy
end
it 'returns false if the name does not exist' do
emoji_exists = described_class.emoji_exists?('random')
expect(emoji_exists).to be_falsey
end
end
describe '.gl_emoji_tag' do
it 'returns gl emoji tag if emoji is found' do
gl_tag = described_class.gl_emoji_tag('small_airplane')

View File

@ -2187,34 +2187,47 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
'gitaly_address' => Gitlab.config.repositories.storages.default.gitaly_address,
'path' => TestEnv::SECOND_STORAGE_PATH
})
new_repository.create_repository
end
after do
new_repository.remove
end
it 'mirrors the source repository' do
subject
expect(refs(new_repository_path)).to eq(refs(repository_path))
end
context 'with keep-around refs' do
let(:sha) { SeedRepo::Commit::ID }
let(:keep_around_ref) { "refs/keep-around/#{sha}" }
let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
before do
repository.write_ref(keep_around_ref, sha)
repository.write_ref(tmp_ref, sha)
end
it 'includes the temporary and keep-around refs' do
context 'destination does not exist' do
it 'mirrors the source repository' do
subject
expect(refs(new_repository_path)).to include(keep_around_ref)
expect(refs(new_repository_path)).to include(tmp_ref)
expect(refs(new_repository_path)).to eq(refs(repository_path))
end
end
context 'destination exists' do
before do
new_repository.create_repository
end
it 'mirrors the source repository' do
subject
expect(refs(new_repository_path)).to eq(refs(repository_path))
end
context 'with keep-around refs' do
let(:sha) { SeedRepo::Commit::ID }
let(:keep_around_ref) { "refs/keep-around/#{sha}" }
let(:tmp_ref) { "refs/tmp/#{SecureRandom.hex}" }
before do
repository.write_ref(keep_around_ref, sha)
repository.write_ref(tmp_ref, sha)
end
it 'includes the temporary and keep-around refs' do
subject
expect(refs(new_repository_path)).to include(keep_around_ref)
expect(refs(new_repository_path)).to include(tmp_ref)
end
end
end
end

View File

@ -387,12 +387,16 @@ RSpec.describe Gitlab::GitalyClient::CommitService do
end
describe '#list_commits_by_ref_name' do
it 'lists latest commits grouped by a ref name' do
response = client.list_commits_by_ref_name(%w[master feature v1.0.0 nonexistent])
let(:project) { create(:project, :repository, create_branch: 'ü/unicode/multi-byte') }
it 'lists latest commits grouped by a ref name' do
response = client.list_commits_by_ref_name(%w[master feature v1.0.0 nonexistent ü/unicode/multi-byte])
expect(response.keys.count).to eq 4
expect(response.fetch('master').id).to eq 'b83d6e391c22777fca1ed3012fce84f633d7fed0'
expect(response.fetch('feature').id).to eq '0b4bc9a49b562e85de7cc9e834518ea6828729b9'
expect(response.fetch('v1.0.0').id).to eq '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
expect(response.fetch('ü/unicode/multi-byte')).to be_present
expect(response).not_to have_key 'nonexistent'
end
end

View File

@ -533,6 +533,7 @@ Project:
- merge_requests_enabled
- wiki_enabled
- snippets_enabled
- requirements_enabled
- visibility_level
- archived
- created_at
@ -600,6 +601,7 @@ ProjectFeature:
- repository_access_level
- pages_access_level
- metrics_dashboard_access_level
- requirements_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:

View File

@ -1489,8 +1489,31 @@ RSpec.describe Ci::Pipeline, :mailer do
end
describe '#lazy_ref_commit' do
let(:another) do
create(:ci_pipeline,
project: project,
ref: 'feature',
sha: project.commit('feature').sha)
end
let(:unicode) do
create(:ci_pipeline,
project: project,
ref: 'ü/unicode/multi-byte')
end
it 'returns the latest commit for a ref lazily' do
expect(pipeline.lazy_ref_commit.id).to eq project.commit(pipeline.ref).id
expect(project.repository)
.to receive(:list_commits_by_ref_name).once
.and_call_original
pipeline.lazy_ref_commit
another.lazy_ref_commit
unicode.lazy_ref_commit
expect(pipeline.lazy_ref_commit.id).to eq pipeline.sha
expect(another.lazy_ref_commit.id).to eq another.sha
expect(unicode.lazy_ref_commit).to be_nil
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe CustomEmoji do
describe 'Associations' do
it { is_expected.to belong_to(:namespace) }
it { is_expected.to have_db_column(:file) }
it { is_expected.to validate_length_of(:name).is_at_most(36) }
it { is_expected.to validate_presence_of(:name) }
end
describe 'exclusion of duplicated emoji' do
let(:emoji_name) { Gitlab::Emoji.emojis_names.sample }
it 'disallows emoji names of built-in emoji' do
new_emoji = build(:custom_emoji, name: emoji_name)
expect(new_emoji).not_to be_valid
expect(new_emoji.errors.messages).to eq(name: ["#{emoji_name} is already being used for another emoji"])
end
it 'disallows duplicate custom emoji names within namespace' do
old_emoji = create(:custom_emoji)
new_emoji = build(:custom_emoji, name: old_emoji.name, namespace: old_emoji.namespace)
expect(new_emoji).not_to be_valid
expect(new_emoji.errors.messages).to eq(name: ["has already been taken"])
end
end
end

View File

@ -17,6 +17,7 @@ RSpec.describe Namespace do
it { is_expected.to have_many :children }
it { is_expected.to have_one :root_storage_statistics }
it { is_expected.to have_one :aggregation_schedule }
it { is_expected.to have_many :custom_emoji }
end
describe 'validations' do

View File

@ -80,6 +80,7 @@ RSpec.describe ProjectPolicy do
let(:additional_guest_permissions) { [] }
let(:additional_reporter_permissions) { [] }
let(:additional_maintainer_permissions) { [] }
let(:additional_owner_permissions) { [] }
let(:guest_permissions) { base_guest_permissions + additional_guest_permissions }
let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions }

View File

@ -37,8 +37,6 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
project.repository.path_to_repo
end
expect(project_repository_double).to receive(:create_repository)
.and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
@ -72,8 +70,6 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
expect(project_repository_double).to receive(:create_repository)
.and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
.and_raise(Gitlab::Git::CommandError)
@ -92,8 +88,6 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
expect(project_repository_double).to receive(:create_repository)
.and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)
@ -115,8 +109,6 @@ RSpec.describe Projects::UpdateRepositoryStorageService do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
expect(project_repository_double).to receive(:create_repository)
.and_return(true)
expect(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
expect(project_repository_double).to receive(:checksum)

View File

@ -45,6 +45,27 @@ RSpec.describe ResourceAccessTokens::CreateService do
expect(access_token.user.reload.user_type).to eq("#{resource_type}_bot")
end
context 'email confirmation status' do
shared_examples_for 'creates a user that has their email confirmed' do
it 'creates a user that has their email confirmed' do
response = subject
access_token = response.payload[:access_token]
expect(access_token.user.reload.confirmed?).to eq(true)
end
end
context 'when created by an admin' do
it_behaves_like 'creates a user that has their email confirmed' do
let(:user) { create(:admin) }
end
end
context 'when created by a non-admin' do
it_behaves_like 'creates a user that has their email confirmed'
end
end
context 'bot name' do
context 'when no value is passed' do
it 'uses default value' do

View File

@ -7,6 +7,12 @@ module MetricsDashboardHelpers
create(:project, :custom_repo, files: { dashboard_path => dashboard_yml })
end
def project_with_dashboard_namespace(dashboard_path, dashboard_yml = nil)
dashboard_yml ||= fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')
create(:project, :custom_repo, namespace: namespace, path: 'monitor-project', files: { dashboard_path => dashboard_yml })
end
def delete_project_dashboard(project, user, dashboard_path)
project.repository.delete_file(
user,

View File

@ -25,15 +25,11 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
allow(project_repository_double).to receive(:create_repository)
.and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
allow(repository_double).to receive(:create_repository)
.and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)
@ -104,15 +100,11 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
it 'unmarks the repository as read-only without updating the repository storage' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
allow(project_repository_double).to receive(:create_repository)
.and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
allow(repository_double).to receive(:create_repository)
.and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
.and_raise(Gitlab::Git::CommandError)
@ -131,15 +123,11 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type|
it 'unmarks the repository as read-only without updating the repository storage' do
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('default').and_call_original
allow(Gitlab::GitalyClient).to receive(:filesystem_id).with('test_second_storage').and_return(SecureRandom.uuid)
allow(project_repository_double).to receive(:create_repository)
.and_return(true)
allow(project_repository_double).to receive(:replicate)
.with(project.repository.raw)
allow(project_repository_double).to receive(:checksum)
.and_return(project_repository_checksum)
allow(repository_double).to receive(:create_repository)
.and_return(true)
allow(repository_double).to receive(:replicate)
.with(repository.raw)
allow(repository_double).to receive(:checksum)