Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-21 09:09:15 +00:00
parent 430aebe8af
commit d89b82481b
128 changed files with 1024 additions and 106 deletions

View File

@ -113,10 +113,10 @@
############################
# rspec job parallel configs
.rspec-migration-parallel:
parallel: 5
parallel: 7
.rspec-ee-migration-parallel:
parallel: 2
parallel: 3
.rspec-unit-parallel:
parallel: 20

View File

@ -420,7 +420,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- ee/spec/controllers/subscriptions_controller_spec.rb
- ee/spec/features/boards/group_boards/multiple_boards_spec.rb
- ee/spec/features/ci_shared_runner_warnings_spec.rb
- ee/spec/features/dashboards/todos_spec.rb
- ee/spec/features/groups/groups_security_credentials_spec.rb
- ee/spec/features/groups/hooks/user_edits_hooks_spec.rb
- ee/spec/features/groups/iterations/user_edits_iteration_spec.rb
@ -655,25 +654,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/lib/extracts_path_spec.rb
- spec/lib/extracts_ref_spec.rb
- spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
- spec/lib/gitlab/ci/build/policy/changes_spec.rb
- spec/lib/gitlab/ci/config/external/file/local_spec.rb
- spec/lib/gitlab/ci/config/external/file/project_spec.rb
- spec/lib/gitlab/ci/config/external/file/template_spec.rb
- spec/lib/gitlab/ci/config/external/mapper_spec.rb
- spec/lib/gitlab/ci/config/external/processor_spec.rb
- spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
- spec/lib/gitlab/ci/pipeline/chain/cancel_pending_pipelines_spec.rb
- spec/lib/gitlab/ci/pipeline/chain/template_usage_spec.rb
- spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb
- spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
- spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
- spec/lib/gitlab/ci/pipeline/seed/deployment_spec.rb
- spec/lib/gitlab/ci/pipeline/seed/environment_spec.rb
- spec/lib/gitlab/ci/pipeline/seed/processable/resource_group_spec.rb
- spec/lib/gitlab/ci/reports/test_failure_history_spec.rb
- spec/lib/gitlab/ci/syntax_templates_spec.rb
- spec/lib/gitlab/ci/trace/chunked_io_spec.rb
- spec/lib/gitlab/ci/trace_spec.rb
- spec/lib/gitlab/closing_issue_extractor_spec.rb
- spec/lib/gitlab/composer/cache_spec.rb
- spec/lib/gitlab/data_builder/wiki_page_spec.rb
@ -710,16 +690,9 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/models/note_spec.rb
- spec/models/notification_setting_spec.rb
- spec/models/operations/feature_flag_spec.rb
- spec/models/packages/dependency_spec.rb
- spec/models/packages/go/module_version_spec.rb
- spec/models/packages/package_spec.rb
- spec/models/packages/tag_spec.rb
- spec/models/plan_limits_spec.rb
- spec/models/prometheus_alert_spec.rb
- spec/models/protected_branch/push_access_level_spec.rb
- spec/models/release_spec.rb
- spec/models/releases/evidence_spec.rb
- spec/models/releases/source_spec.rb
- spec/models/repository_spec.rb
- spec/models/service_spec.rb
- spec/models/snippet_repository_spec.rb
@ -729,18 +702,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/models/user_spec.rb
- spec/models/wiki_page/meta_spec.rb
- spec/models/wiki_page_spec.rb
- spec/presenters/alert_management/alert_presenter_spec.rb
- spec/presenters/ci/pipeline_presenter_spec.rb
- spec/presenters/label_presenter_spec.rb
- spec/presenters/packages/composer/packages_presenter_spec.rb
- spec/presenters/packages/conan/package_presenter_spec.rb
- spec/presenters/packages/detail/package_presenter_spec.rb
- spec/presenters/packages/npm/package_presenter_spec.rb
- spec/presenters/packages/nuget/search_results_presenter_spec.rb
- spec/presenters/project_presenter_spec.rb
- spec/presenters/prometheus_alert_presenter_spec.rb
- spec/presenters/release_presenter_spec.rb
- spec/presenters/user_presenter_spec.rb
- spec/requests/api/api_spec.rb
- spec/requests/api/award_emoji_spec.rb
- spec/requests/api/branches_spec.rb
@ -839,28 +800,6 @@ RSpec/EmptyLineAfterFinalLetItBe:
- spec/services/auth/dependency_proxy_authentication_service_spec.rb
- spec/services/auto_merge_service_spec.rb
- spec/services/bulk_create_integration_service_spec.rb
- spec/services/ci/change_variable_service_spec.rb
- spec/services/ci/change_variables_service_spec.rb
- spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb
- spec/services/ci/create_pipeline_service/custom_config_content_spec.rb
- spec/services/ci/create_pipeline_service/dry_run_spec.rb
- spec/services/ci/create_pipeline_service/environment_spec.rb
- spec/services/ci/create_pipeline_service/parameter_content_spec.rb
- spec/services/ci/create_pipeline_service/parent_child_pipeline_spec.rb
- spec/services/ci/create_pipeline_service_spec.rb
- spec/services/ci/create_web_ide_terminal_service_spec.rb
- spec/services/ci/expire_pipeline_cache_service_spec.rb
- spec/services/ci/external_pull_requests/create_pipeline_service_spec.rb
- spec/services/ci/find_exposed_artifacts_service_spec.rb
- spec/services/ci/job_artifacts/create_service_spec.rb
- spec/services/ci/parse_dotenv_artifact_service_spec.rb
- spec/services/ci/pipeline_bridge_status_service_spec.rb
- spec/services/ci/pipeline_trigger_service_spec.rb
- spec/services/ci/prometheus_metrics/observe_histograms_service_spec.rb
- spec/services/ci/register_job_service_spec.rb
- spec/services/ci/resource_groups/assign_resource_from_resource_group_service_spec.rb
- spec/services/ci/retry_build_service_spec.rb
- spec/services/ci/stop_environments_service_spec.rb
- spec/services/clusters/applications/prometheus_health_check_service_spec.rb
- spec/services/container_expiration_policy_service_spec.rb
- spec/services/dependency_proxy/find_or_create_manifest_service_spec.rb

View File

@ -0,0 +1 @@
export const DEFAULT_DAYS_TO_DISPLAY = 30;

View File

@ -0,0 +1,51 @@
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import { DEFAULT_DAYS_TO_DISPLAY } from '../constants';
import * as types from './mutation_types';
export const fetchCycleAnalyticsData = ({
state: { requestPath, startDate },
dispatch,
commit,
}) => {
commit(types.REQUEST_CYCLE_ANALYTICS_DATA);
return axios
.get(requestPath, {
params: { 'cycle_analytics[start_date]': startDate },
})
.then(({ data }) => commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS, data))
.then(() => dispatch('setSelectedStage'))
.then(() => dispatch('fetchStageData'))
.catch(() => {
commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR);
createFlash({
message: __('There was an error while fetching value stream analytics data.'),
});
});
};
export const fetchStageData = ({ state: { requestPath, selectedStage, startDate }, commit }) => {
commit(types.REQUEST_STAGE_DATA);
return axios
.get(`${requestPath}/events/${selectedStage.name}.json`, {
params: { 'cycle_analytics[start_date]': startDate },
})
.then(({ data }) => commit(types.RECEIVE_STAGE_DATA_SUCCESS, data))
.catch(() => commit(types.RECEIVE_STAGE_DATA_ERROR));
};
export const setSelectedStage = ({ commit, state: { stages } }, selectedStage = null) => {
const stage = selectedStage || stages[0];
commit(types.SET_SELECTED_STAGE, stage);
};
export const setDateRange = ({ commit }, { startDate = DEFAULT_DAYS_TO_DISPLAY }) =>
commit(types.SET_DATE_RANGE, { startDate });
export const initializeVsa = ({ commit, dispatch }, initialData = {}) => {
commit(types.INITIALIZE_VSA, initialData);
return dispatch('fetchCycleAnalyticsData');
};

View File

@ -0,0 +1,21 @@
/**
* While we are in the process implementing group level features at the project level
* we will use a simplified vuex store for the project level, eventually this can be
* replaced with the store at ee/app/assets/javascripts/analytics/cycle_analytics/store/index.js
* once we have enough of the same features implemented across the project and group level
*/
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export default () =>
new Vuex.Store({
actions,
mutations,
state,
});

View File

@ -0,0 +1,12 @@
export const INITIALIZE_VSA = 'INITIALIZE_VSA';
export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
export const SET_DATE_RANGE = 'SET_DATE_RANGE';
export const REQUEST_CYCLE_ANALYTICS_DATA = 'REQUEST_CYCLE_ANALYTICS_DATA';
export const RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS = 'RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS';
export const RECEIVE_CYCLE_ANALYTICS_DATA_ERROR = 'RECEIVE_CYCLE_ANALYTICS_DATA_ERROR';
export const REQUEST_STAGE_DATA = 'REQUEST_STAGE_DATA';
export const RECEIVE_STAGE_DATA_SUCCESS = 'RECEIVE_STAGE_DATA_SUCCESS';
export const RECEIVE_STAGE_DATA_ERROR = 'RECEIVE_STAGE_DATA_ERROR';

View File

@ -0,0 +1,52 @@
import { decorateData, decorateEvents } from '../utils';
import * as types from './mutation_types';
export default {
[types.INITIALIZE_VSA](state, { requestPath }) {
state.requestPath = requestPath;
},
[types.SET_SELECTED_STAGE](state, stage) {
state.isLoadingStage = true;
state.selectedStage = stage;
state.isLoadingStage = false;
},
[types.SET_DATE_RANGE](state, { startDate }) {
state.startDate = startDate;
},
[types.REQUEST_CYCLE_ANALYTICS_DATA](state) {
state.isLoading = true;
state.stages = [];
state.hasError = false;
},
[types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state, data) {
state.isLoading = false;
const { stages, summary } = decorateData(data);
state.stages = stages;
state.summary = summary;
state.hasError = false;
},
[types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR](state) {
state.isLoading = false;
state.stages = [];
state.hasError = true;
},
[types.REQUEST_STAGE_DATA](state) {
state.isLoadingStage = true;
state.isEmptyStage = false;
state.selectedStageEvents = [];
state.hasError = false;
},
[types.RECEIVE_STAGE_DATA_SUCCESS](state, { events = [] }) {
const { selectedStage } = state;
state.isLoadingStage = false;
state.isEmptyStage = !events.length;
state.selectedStageEvents = decorateEvents(events, selectedStage);
state.hasError = false;
},
[types.RECEIVE_STAGE_DATA_ERROR](state) {
state.isLoadingStage = false;
state.isEmptyStage = true;
state.selectedStageEvents = [];
state.hasError = true;
},
};

View File

@ -0,0 +1,17 @@
import { DEFAULT_DAYS_TO_DISPLAY } from '../constants';
export default () => ({
requestPath: '',
startDate: DEFAULT_DAYS_TO_DISPLAY,
stages: [],
summary: [],
analytics: [],
stats: [],
selectedStage: {},
selectedStageEvents: [],
medians: {},
hasError: false,
isLoading: false,
isLoadingStage: false,
isEmptyStage: false,
});

View File

@ -0,0 +1,63 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { dasherize } from '~/lib/utils/text_utility';
import { __ } from '../locale';
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
const EMPTY_STAGE_TEXTS = {
issue: __(
'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
),
plan: __(
'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
),
code: __(
'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
),
test: __(
'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
),
review: __(
'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
),
staging: __(
'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
),
};
/**
* These `decorate` methods will be removed when me migrate to the
* new table layout https://gitlab.com/gitlab-org/gitlab/-/issues/326704
*/
const mapToEvent = (event, stage) => {
return convertObjectPropsToCamelCase(
{
...DEFAULT_EVENT_OBJECTS[stage.slug],
...event,
},
{ deep: true },
);
};
export const decorateEvents = (events, stage) => events.map((event) => mapToEvent(event, stage));
const mapToStage = (permissions, item) => {
const slug = dasherize(item.name.toLowerCase());
return {
...item,
slug,
active: false,
isUserAllowed: permissions[slug],
emptyStageText: EMPTY_STAGE_TEXTS[slug],
component: `stage-${slug}-component`,
};
};
const mapToSummary = ({ value, ...rest }) => ({ ...rest, value: value || '-' });
export const decorateData = (data = {}) => {
const { permissions, stats, summary } = data;
return {
stages: stats?.map((item) => mapToStage(permissions, item)) || [],
summary: summary?.map((item) => mapToSummary(item)) || [],
};
};

View File

@ -1,6 +1,9 @@
<script>
import { GlFormGroup, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { NAME_REGEX_LENGTH, TEXT_AREA_INVALID_FEEDBACK } from '../constants';
import {
NAME_REGEX_LENGTH,
TEXT_AREA_INVALID_FEEDBACK,
} from '~/packages_and_registries/settings/project/constants';
export default {
components: {

View File

@ -1,6 +1,9 @@
<script>
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
import { NEXT_CLEANUP_LABEL, NOT_SCHEDULED_POLICY_TEXT } from '~/registry/settings/constants';
import {
NEXT_CLEANUP_LABEL,
NOT_SCHEDULED_POLICY_TEXT,
} from '~/packages_and_registries/settings/project/constants';
export default {
components: {

View File

@ -1,7 +1,10 @@
<script>
import { GlFormGroup, GlToggle, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import { ENABLED_TOGGLE_DESCRIPTION, DISABLED_TOGGLE_DESCRIPTION } from '../constants';
import {
ENABLED_TOGGLE_DESCRIPTION,
DISABLED_TOGGLE_DESCRIPTION,
} from '~/packages_and_registries/settings/project/constants';
export default {
i18n: {

View File

@ -7,8 +7,8 @@ import {
UNAVAILABLE_FEATURE_INTRO_TEXT,
UNAVAILABLE_USER_FEATURE_TEXT,
UNAVAILABLE_ADMIN_FEATURE_TEXT,
} from '../constants';
import expirationPolicyQuery from '../graphql/queries/get_expiration_policy.query.graphql';
} from '~/packages_and_registries/settings/project/constants';
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
import SettingsForm from './settings_form.vue';

View File

@ -17,10 +17,10 @@ import {
NAME_REGEX_DESCRIPTION,
CADENCE_LABEL,
EXPIRATION_POLICY_FOOTER_NOTE,
} from '~/registry/settings/constants';
import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql';
import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update';
import { formOptionsGenerator } from '~/registry/settings/utils';
} from '~/packages_and_registries/settings/project/constants';
import updateContainerExpirationPolicyMutation from '~/packages_and_registries/settings/project/graphql/mutations/update_container_expiration_policy.mutation.graphql';
import { updateContainerExpirationPolicy } from '~/packages_and_registries/settings/project/graphql/utils/cache_update';
import { formOptionsGenerator } from '~/packages_and_registries/settings/project/utils';
import Tracking from '~/tracking';
import ExpirationDropdown from './expiration_dropdown.vue';
import ExpirationInput from './expiration_input.vue';

View File

@ -3,9 +3,9 @@ import SecretValues from '~/behaviors/secret_values';
import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
import initVariableList from '~/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze';
import registrySettingsApp from '~/packages_and_registries/settings/project/registry_settings_bundle';
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
import initSharedRunnersToggle from '~/projects/settings/mount_shared_runners_toggle';
import registrySettingsApp from '~/registry/settings/registry_settings_bundle';
import initSettingsPanels from '~/settings_panels';
document.addEventListener('DOMContentLoaded', () => {

View File

@ -4,6 +4,10 @@ module Types
module Boards
# rubocop: disable Graphql/AuthorizeTypes
class BoardIssueInputBaseType < BoardIssuableInputBaseType
argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'List of IIDs of issues. For example ["1", "2"].'
argument :milestone_title, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by milestone title.'

View File

@ -8,6 +8,7 @@ module Clusters
belongs_to :project, class_name: '::Project' # Otherwise, it will load ::Clusters::Project
has_many :agent_tokens, class_name: 'Clusters::AgentToken'
has_many :last_used_agent_tokens, -> { order_last_used_at_desc }, class_name: 'Clusters::AgentToken', inverse_of: :agent
scope :ordered_by_name, -> { order(:name) }
scope :with_name, -> (name) { where(name: name) }

View File

@ -6,7 +6,7 @@ module Clusters
include TokenAuthenticatable
add_authentication_token_field :token, encrypted: :required, token_generator: -> { Devise.friendly_token(50) }
cached_attr_reader :last_contacted_at
cached_attr_reader :last_used_at
self.table_name = 'cluster_agent_tokens'
@ -21,6 +21,8 @@ module Clusters
validates :description, length: { maximum: 1024 }
validates :name, presence: true, length: { maximum: 255 }
scope :order_last_used_at_desc, -> { order(::Gitlab::Database.nulls_last_order('last_used_at', 'DESC')) }
def track_usage
track_values = { last_used_at: Time.current.utc }

View File

@ -0,0 +1,5 @@
---
title: Support board issue filtering by iids in GraphQL
merge_request: 59703
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add ability to order cluster token by last used
merge_request: 59716
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Report summarized Gitaly Apdex via usage ping
merge_request: 47040
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/lib/gitlab/ci
merge_request: 58249
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/models/packages
merge_request: 58370
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/models/releases
merge_request: 58384
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/presenters
merge_request: 58405
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix EmptyLineAfterFinalLetItBe offenses in spec/services/ci
merge_request: 58411
author: Huzaifa Iftikhar @huzaifaiftikhar
type: fixed

View File

@ -0,0 +1,20 @@
---
key_path: settings.gitaly_apdex
description: Gitaly application performance
product_section: dev
product_stage: create
product_group: group::gitaly
product_category: gitaly
value_type: number
status: implemented
milestone: "13.11"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/47040
time_frame: none
data_source: prometheus
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class IndexClusterAgentTokensOnLastUsedAt < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
OLD_INDEX = 'index_cluster_agent_tokens_on_agent_id'
NEW_INDEX = 'index_cluster_agent_tokens_on_agent_id_and_last_used_at'
disable_ddl_transaction!
def up
add_concurrent_index :cluster_agent_tokens, 'agent_id, last_used_at DESC NULLS LAST', name: NEW_INDEX
remove_concurrent_index_by_name :cluster_agent_tokens, OLD_INDEX
end
def down
add_concurrent_index :cluster_agent_tokens, :agent_id, name: OLD_INDEX
remove_concurrent_index_by_name :cluster_agent_tokens, NEW_INDEX
end
end

View File

@ -0,0 +1 @@
c9e8c49bf272ef49d906431bdc11a24abe967a9d7e95976d70c48b21b48a062b

View File

@ -22324,7 +22324,7 @@ CREATE INDEX index_ci_variables_on_key ON ci_variables USING btree (key);
CREATE UNIQUE INDEX index_ci_variables_on_project_id_and_key_and_environment_scope ON ci_variables USING btree (project_id, key, environment_scope);
CREATE INDEX index_cluster_agent_tokens_on_agent_id ON cluster_agent_tokens USING btree (agent_id);
CREATE INDEX index_cluster_agent_tokens_on_agent_id_and_last_used_at ON cluster_agent_tokens USING btree (agent_id, last_used_at DESC NULLS LAST);
CREATE INDEX index_cluster_agent_tokens_on_created_by_user_id ON cluster_agent_tokens USING btree (created_by_user_id);

View File

@ -8756,6 +8756,15 @@ The state of the vulnerability.
| `DISMISSED` | |
| `RESOLVED` | |
### `WeightWildcardId`
Weight ID wildcard values.
| Value | Description |
| ----- | ----------- |
| `ANY` | Weight is assigned. |
| `NONE` | No weight is assigned. |
## Scalar types
Scalar values are atomic values, and do not have fields of their own.

View File

@ -198,3 +198,10 @@ export default {
<img :src="svgIllustrationPath" />
</template>
```
### Minimize SVGs
When you develop or export a new SVG illustration, minimize it with an [SVGO](https://github.com/svg/svgo) powered tool, like
[SVGOMG](https://jakearchibald.github.io/svgomg/), to save space. Illustrations
added to [GitLab SVG](https://gitlab.com/gitlab-org/gitlab-svgs) are automatically
minimized, so no manual action is needed.

View File

@ -362,3 +362,7 @@ This uses GraphQL Ruby's built-in Rake tasks to generate files in both [IDL](htt
### Update documentation and schema definitions
The following command combines the intent of [Update GraphQL documentation and schema definitions](#update-graphql-documentation-and-schema-definitions) and [Update machine-readable schema files](#update-machine-readable-schema-files):
```shell
bundle exec rake gitlab:graphql:update_all
```

View File

@ -14360,6 +14360,18 @@ Status: `data_available`
Tiers: `free`, `premium`, `ultimate`
### `settings.gitaly_apdex`
Gitaly application performance
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/settings/20210321224827_gitaly_apdex.yml)
Group: `group::gitaly`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `settings.ldap_encrypted_secrets_enabled`
Is encrypted LDAP secrets configured?

View File

@ -12,6 +12,8 @@ module Gitlab
class DataCollector
include Gitlab::Utils::StrongMemoize
MAX_COUNT = 1001
delegate :serialized_records, to: :records_fetcher
def initialize(stage:, params: {})
@ -37,6 +39,12 @@ module Gitlab
end
end
def count
strong_memoize(:count) do
limit_count
end
end
private
attr_reader :stage, :params
@ -44,6 +52,13 @@ module Gitlab
def query
BaseQueryBuilder.new(stage: stage, params: params).build
end
# Limiting the maximum number of records so the COUNT(*) query stays efficient for large groups.
# COUNT = 1001, show 1000+ on the UI
# COUNT < 1001, show the actual number on the UI
def limit_count
query.limit(MAX_COUNT).count
end
end
end
end

View File

@ -243,7 +243,8 @@ module Gitlab
{
settings: {
ldap_encrypted_secrets_enabled: alt_usage_data(fallback: nil) { Gitlab::Auth::Ldap::Config.encrypted_secrets.active? },
operating_system: alt_usage_data(fallback: nil) { operating_system }
operating_system: alt_usage_data(fallback: nil) { operating_system },
gitaly_apdex: alt_usage_data { gitaly_apdex }
}
}
end
@ -767,6 +768,16 @@ module Gitlab
private
def gitaly_apdex
with_prometheus_client(verify: false, fallback: FALLBACK) do |client|
result = client.query('avg_over_time(gitlab_usage_ping:gitaly_apdex:ratio_avg_over_time_5m[1w])').first
break FALLBACK unless result
result['value'].last.to_f
end
end
def aggregated_metrics
@aggregated_metrics ||= ::Gitlab::Usage::Metrics::Aggregates::Aggregate.new(recorded_at)
end

View File

@ -14368,6 +14368,9 @@ msgstr ""
msgid "Geo|Geo sites"
msgstr ""
msgid "Geo|Geo supports replication of many data types."
msgstr ""
msgid "Geo|Go to the primary site"
msgstr ""
@ -14476,6 +14479,12 @@ msgstr ""
msgid "Geo|Replication Details"
msgstr ""
msgid "Geo|Replication Details Desktop"
msgstr ""
msgid "Geo|Replication Details Mobile"
msgstr ""
msgid "Geo|Replication details"
msgstr ""
@ -34593,6 +34602,9 @@ msgstr ""
msgid "ValueStreamAnalytics|Median time from issue created to issue closed."
msgstr ""
msgid "ValueStreamEvent|Stage time (median)"
msgstr ""
msgid "ValueStreamEvent|Start"
msgstr ""

View File

@ -3,14 +3,14 @@
require 'fileutils'
require_relative 'gitaly_test'
require_relative '../spec/support/helpers/gitaly_setup'
# This script assumes tmp/tests/gitaly already contains the correct
# Gitaly version. We just have to compile it and run its 'bundle
# install'. We have this separate script for that to avoid bundle
# poisoning in CI. This script should only be run in CI.
class GitalyTestBuild
include GitalyTest
include GitalySetup
def run
set_bundler_config

View File

@ -3,10 +3,10 @@
# This script is used both in CI and in local development 'rspec' runs.
require_relative 'gitaly_test'
require_relative '../spec/support/helpers/gitaly_setup'
class GitalyTestSpawn
include GitalyTest
include GitalySetup
def run
set_bundler_config

View File

@ -0,0 +1,186 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
export const summary = [
{ value: '20', title: 'New Issues' },
{ value: null, title: 'Commits' },
{ value: null, title: 'Deploys' },
{ value: null, title: 'Deployment Frequency', unit: 'per day' },
];
const issueStage = {
title: 'Issue',
name: 'issue',
legend: '',
description: 'Time before an issue gets scheduled',
value: null,
};
const planStage = {
title: 'Plan',
name: 'plan',
legend: '',
description: 'Time before an issue starts implementation',
value: 'about 21 hours',
};
const codeStage = {
title: 'Code',
name: 'code',
legend: '',
description: 'Time until first merge request',
value: '2 days',
};
const testStage = {
title: 'Test',
name: 'test',
legend: '',
description: 'Total test time for all commits/merges',
value: 'about 5 hours',
};
const reviewStage = {
title: 'Review',
name: 'review',
legend: '',
description: 'Time between merge request creation and merge/close',
value: null,
};
const stagingStage = {
title: 'Staging',
name: 'staging',
legend: '',
description: 'From merge request merge until deploy to production',
value: '2 days',
};
export const selectedStage = {
...issueStage,
value: null,
active: false,
isUserAllowed: true,
emptyStageText:
'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
component: 'stage-issue-component',
slug: 'issue',
};
export const stats = [issueStage, planStage, codeStage, testStage, reviewStage, stagingStage];
export const permissions = {
issue: true,
plan: true,
code: true,
test: true,
review: true,
staging: true,
};
export const rawData = {
summary,
stats,
permissions,
};
export const convertedData = {
stages: [
selectedStage,
{
...planStage,
active: false,
isUserAllowed: true,
emptyStageText:
'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
component: 'stage-plan-component',
slug: 'plan',
},
{
...codeStage,
active: false,
isUserAllowed: true,
emptyStageText:
'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
component: 'stage-code-component',
slug: 'code',
},
{
...testStage,
active: false,
isUserAllowed: true,
emptyStageText:
'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
component: 'stage-test-component',
slug: 'test',
},
{
...reviewStage,
active: false,
isUserAllowed: true,
emptyStageText:
'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
component: 'stage-review-component',
slug: 'review',
},
{
...stagingStage,
active: false,
isUserAllowed: true,
emptyStageText:
'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
component: 'stage-staging-component',
slug: 'staging',
},
],
summary: [
{ value: '20', title: 'New Issues' },
{ value: '-', title: 'Commits' },
{ value: '-', title: 'Deploys' },
{ value: '-', title: 'Deployment Frequency', unit: 'per day' },
],
};
export const rawEvents = [
{
title: 'Brockfunc-1617160796',
author: {
id: 275,
name: 'VSM User4',
username: 'vsm-user-4-1617160796',
state: 'active',
avatar_url:
'https://www.gravatar.com/avatar/6a6f5480ae582ba68982a34169420747?s=80&d=identicon',
web_url: 'http://gdk.test:3001/vsm-user-4-1617160796',
show_status: false,
path: '/vsm-user-4-1617160796',
},
iid: '16',
total_time: { days: 1, hours: 9 },
created_at: 'about 1 month ago',
url: 'http://gdk.test:3001/vsa-life/ror-project-vsa/-/issues/16',
short_sha: 'some_sha',
commit_url: 'some_commit_url',
},
{
title: 'Subpod-1617160796',
author: {
id: 274,
name: 'VSM User3',
username: 'vsm-user-3-1617160796',
state: 'active',
avatar_url:
'https://www.gravatar.com/avatar/fde853fc3ab7dc552e649dcb4fcf5f7f?s=80&d=identicon',
web_url: 'http://gdk.test:3001/vsm-user-3-1617160796',
show_status: false,
path: '/vsm-user-3-1617160796',
},
iid: '20',
total_time: { days: 2, hours: 18 },
created_at: 'about 1 month ago',
url: 'http://gdk.test:3001/vsa-life/ror-project-vsa/-/issues/20',
},
];
export const convertedEvents = rawEvents.map((ev) =>
convertObjectPropsToCamelCase(ev, { deep: true }),
);

View File

@ -0,0 +1,130 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/cycle_analytics/store/actions';
import httpStatusCodes from '~/lib/utils/http_status';
import { selectedStage } from '../mock_data';
const mockRequestPath = 'some/cool/path';
const mockStartDate = 30;
describe('Project Value Stream Analytics actions', () => {
let state;
let mock;
beforeEach(() => {
state = {};
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
state = {};
});
it.each`
action | type | payload | expectedActions
${'initializeVsa'} | ${'INITIALIZE_VSA'} | ${{ requestPath: mockRequestPath }} | ${['fetchCycleAnalyticsData']}
${'setDateRange'} | ${'SET_DATE_RANGE'} | ${{ startDate: 30 }} | ${[]}
${'setSelectedStage'} | ${'SET_SELECTED_STAGE'} | ${{ selectedStage }} | ${[]}
`(
'$action should dispatch $expectedActions and commit $type',
({ action, type, payload, expectedActions }) =>
testAction({
action: actions[action],
state,
payload,
expectedMutations: [
{
type,
payload,
},
],
expectedActions: expectedActions.map((a) => ({ type: a })),
}),
);
describe('fetchCycleAnalyticsData', () => {
beforeEach(() => {
state = { requestPath: mockRequestPath };
mock = new MockAdapter(axios);
mock.onGet(mockRequestPath).reply(httpStatusCodes.OK);
});
it(`dispatches the 'setSelectedStage' and 'fetchStageData' actions`, () =>
testAction({
action: actions.fetchCycleAnalyticsData,
state,
payload: {},
expectedMutations: [
{ type: 'REQUEST_CYCLE_ANALYTICS_DATA' },
{ type: 'RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS' },
],
expectedActions: [{ type: 'setSelectedStage' }, { type: 'fetchStageData' }],
}));
describe('with a failing request', () => {
beforeEach(() => {
state = { requestPath: mockRequestPath };
mock = new MockAdapter(axios);
mock.onGet(mockRequestPath).reply(httpStatusCodes.BAD_REQUEST);
});
it(`commits the 'RECEIVE_CYCLE_ANALYTICS_DATA_ERROR' mutation`, () =>
testAction({
action: actions.fetchCycleAnalyticsData,
state,
payload: {},
expectedMutations: [
{ type: 'REQUEST_CYCLE_ANALYTICS_DATA' },
{ type: 'RECEIVE_CYCLE_ANALYTICS_DATA_ERROR' },
],
expectedActions: [],
}));
});
});
describe('fetchStageData', () => {
const mockStagePath = `${mockRequestPath}/events/${selectedStage.name}.json`;
beforeEach(() => {
state = {
requestPath: mockRequestPath,
startDate: mockStartDate,
selectedStage,
};
mock = new MockAdapter(axios);
mock.onGet(mockStagePath).reply(httpStatusCodes.OK);
});
it(`commits the 'RECEIVE_STAGE_DATA_SUCCESS' mutation`, () =>
testAction({
action: actions.fetchStageData,
state,
payload: {},
expectedMutations: [{ type: 'REQUEST_STAGE_DATA' }, { type: 'RECEIVE_STAGE_DATA_SUCCESS' }],
expectedActions: [],
}));
describe('with a failing request', () => {
beforeEach(() => {
state = {
requestPath: mockRequestPath,
startDate: mockStartDate,
selectedStage,
};
mock = new MockAdapter(axios);
mock.onGet(mockStagePath).reply(httpStatusCodes.BAD_REQUEST);
});
it(`commits the 'RECEIVE_STAGE_DATA_ERROR' mutation`, () =>
testAction({
action: actions.fetchStageData,
state,
payload: {},
expectedMutations: [{ type: 'REQUEST_STAGE_DATA' }, { type: 'RECEIVE_STAGE_DATA_ERROR' }],
expectedActions: [],
}));
});
});
});

View File

@ -0,0 +1,83 @@
import * as types from '~/cycle_analytics/store/mutation_types';
import mutations from '~/cycle_analytics/store/mutations';
import { selectedStage, rawEvents, convertedEvents, rawData, convertedData } from '../mock_data';
let state;
const mockRequestPath = 'fake/request/path';
const mockStartData = '2021-04-20';
describe('Project Value Stream Analytics mutations', () => {
beforeEach(() => {
state = {};
});
afterEach(() => {
state = null;
});
it.each`
mutation | stateKey | value
${types.SET_SELECTED_STAGE} | ${'isLoadingStage'} | ${false}
${types.REQUEST_CYCLE_ANALYTICS_DATA} | ${'isLoading'} | ${true}
${types.REQUEST_CYCLE_ANALYTICS_DATA} | ${'stages'} | ${[]}
${types.REQUEST_CYCLE_ANALYTICS_DATA} | ${'hasError'} | ${false}
${types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS} | ${'isLoading'} | ${false}
${types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS} | ${'hasError'} | ${false}
${types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR} | ${'isLoading'} | ${false}
${types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR} | ${'hasError'} | ${true}
${types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR} | ${'stages'} | ${[]}
${types.REQUEST_STAGE_DATA} | ${'isLoadingStage'} | ${true}
${types.REQUEST_STAGE_DATA} | ${'isEmptyStage'} | ${false}
${types.REQUEST_STAGE_DATA} | ${'hasError'} | ${false}
${types.REQUEST_STAGE_DATA} | ${'selectedStageEvents'} | ${[]}
${types.RECEIVE_STAGE_DATA_SUCCESS} | ${'isLoadingStage'} | ${false}
${types.RECEIVE_STAGE_DATA_SUCCESS} | ${'selectedStageEvents'} | ${[]}
${types.RECEIVE_STAGE_DATA_SUCCESS} | ${'hasError'} | ${false}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isLoadingStage'} | ${false}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'selectedStageEvents'} | ${[]}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'hasError'} | ${true}
${types.RECEIVE_STAGE_DATA_ERROR} | ${'isEmptyStage'} | ${true}
`('$mutation will set $stateKey to $value', ({ mutation, stateKey, value }) => {
mutations[mutation](state, {});
expect(state).toMatchObject({ [stateKey]: value });
});
it.each`
mutation | payload | stateKey | value
${types.INITIALIZE_VSA} | ${{ requestPath: mockRequestPath }} | ${'requestPath'} | ${mockRequestPath}
${types.SET_SELECTED_STAGE} | ${selectedStage} | ${'selectedStage'} | ${selectedStage}
${types.SET_DATE_RANGE} | ${{ startDate: mockStartData }} | ${'startDate'} | ${mockStartData}
${types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS} | ${rawData} | ${'stages'} | ${convertedData.stages}
${types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS} | ${rawData} | ${'summary'} | ${convertedData.summary}
`(
'$mutation with $payload will set $stateKey to $value',
({ mutation, payload, stateKey, value }) => {
mutations[mutation](state, payload);
expect(state).toMatchObject({ [stateKey]: value });
},
);
describe('with a stage selected', () => {
beforeEach(() => {
state = {
selectedStage,
};
});
it.each`
mutation | payload | stateKey | value
${types.RECEIVE_STAGE_DATA_SUCCESS} | ${{ events: [] }} | ${'isEmptyStage'} | ${true}
${types.RECEIVE_STAGE_DATA_SUCCESS} | ${{ events: rawEvents }} | ${'selectedStageEvents'} | ${convertedEvents}
${types.RECEIVE_STAGE_DATA_SUCCESS} | ${{ events: rawEvents }} | ${'isEmptyStage'} | ${false}
`(
'$mutation with $payload will set $stateKey to $value',
({ mutation, payload, stateKey, value }) => {
mutations[mutation](state, payload);
expect(state).toMatchObject({ [stateKey]: value });
},
);
});
});

View File

@ -0,0 +1,77 @@
import { decorateEvents, decorateData } from '~/cycle_analytics/utils';
import { selectedStage, rawData, convertedData, rawEvents } from './mock_data';
describe('Value stream analytics utils', () => {
describe('decorateEvents', () => {
const [result] = decorateEvents(rawEvents, selectedStage);
const eventKeys = Object.keys(result);
const authorKeys = Object.keys(result.author);
it('will return the same number of events', () => {
expect(decorateEvents(rawEvents, selectedStage).length).toBe(rawEvents.length);
});
it('will set all the required event fields', () => {
['totalTime', 'author', 'createdAt', 'shortSha', 'commitUrl'].forEach((key) => {
expect(eventKeys).toContain(key);
});
['webUrl', 'avatarUrl'].forEach((key) => {
expect(authorKeys).toContain(key);
});
});
it('will remove unused fields', () => {
['total_time', 'created_at', 'short_sha', 'commit_url'].forEach((key) => {
expect(eventKeys).not.toContain(key);
});
['web_url', 'avatar_url'].forEach((key) => {
expect(authorKeys).not.toContain(key);
});
});
});
describe('decorateData', () => {
const result = decorateData(rawData);
it('returns the summary data', () => {
expect(result.summary).toEqual(convertedData.summary);
});
it('returns the stages data', () => {
expect(result.stages).toEqual(convertedData.stages);
});
it('returns each of the default value stream stages', () => {
const stages = result.stages.map(({ name }) => name);
['issue', 'plan', 'code', 'test', 'review', 'staging'].forEach((stageName) => {
expect(stages).toContain(stageName);
});
});
it('returns `-` for summary data that has no value', () => {
const singleSummaryResult = decorateData({
stats: [],
permissions: { issue: true },
summary: [{ value: null, title: 'Commits' }],
});
expect(singleSummaryResult.summary).toEqual([{ value: '-', title: 'Commits' }]);
});
it('returns additional fields for each stage', () => {
const singleStageResult = decorateData({
stats: [{ name: 'issue', value: null }],
permissions: { issue: false },
});
const stage = singleStageResult.stages[0];
const txt =
'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.';
expect(stage).toMatchObject({
active: false,
isUserAllowed: false,
emptyStageText: txt,
slug: 'issue',
component: 'stage-issue-component',
});
});
});
});

View File

@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlFormGroup, GlFormSelect } from 'jest/registry/shared/stubs';
import component from '~/registry/settings/components/expiration_dropdown.vue';
import component from '~/packages_and_registries/settings/project/components/expiration_dropdown.vue';
describe('ExpirationDropdown', () => {
let wrapper;

View File

@ -1,8 +1,8 @@
import { GlSprintf, GlFormInput, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlFormGroup } from 'jest/registry/shared/stubs';
import component from '~/registry/settings/components/expiration_input.vue';
import { NAME_REGEX_LENGTH } from '~/registry/settings/constants';
import component from '~/packages_and_registries/settings/project/components/expiration_input.vue';
import { NAME_REGEX_LENGTH } from '~/packages_and_registries/settings/project/constants';
describe('ExpirationInput', () => {
let wrapper;

View File

@ -1,8 +1,11 @@
import { GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlFormGroup } from 'jest/registry/shared/stubs';
import component from '~/registry/settings/components/expiration_run_text.vue';
import { NEXT_CLEANUP_LABEL, NOT_SCHEDULED_POLICY_TEXT } from '~/registry/settings/constants';
import component from '~/packages_and_registries/settings/project/components/expiration_run_text.vue';
import {
NEXT_CLEANUP_LABEL,
NOT_SCHEDULED_POLICY_TEXT,
} from '~/packages_and_registries/settings/project/constants';
describe('ExpirationToggle', () => {
let wrapper;

View File

@ -1,11 +1,11 @@
import { GlToggle, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlFormGroup } from 'jest/registry/shared/stubs';
import component from '~/registry/settings/components/expiration_toggle.vue';
import component from '~/packages_and_registries/settings/project/components/expiration_toggle.vue';
import {
ENABLED_TOGGLE_DESCRIPTION,
DISABLED_TOGGLE_DESCRIPTION,
} from '~/registry/settings/constants';
} from '~/packages_and_registries/settings/project/constants';
describe('ExpirationToggle', () => {
let wrapper;

View File

@ -2,14 +2,14 @@ import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import component from '~/registry/settings/components/registry_settings_app.vue';
import SettingsForm from '~/registry/settings/components/settings_form.vue';
import component from '~/packages_and_registries/settings/project/components/registry_settings_app.vue';
import SettingsForm from '~/packages_and_registries/settings/project/components/settings_form.vue';
import {
FETCH_SETTINGS_ERROR_MESSAGE,
UNAVAILABLE_FEATURE_INTRO_TEXT,
UNAVAILABLE_USER_FEATURE_TEXT,
} from '~/registry/settings/constants';
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
} from '~/packages_and_registries/settings/project/constants';
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
import {
expirationPolicyPayload,

View File

@ -2,15 +2,15 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import component from '~/registry/settings/components/settings_form.vue';
import { GlCard, GlLoadingIcon } from 'jest/registry/shared/stubs';
import component from '~/packages_and_registries/settings/project/components/settings_form.vue';
import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
} from '~/registry/settings/constants';
import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql';
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
} from '~/packages_and_registries/settings/project/constants';
import updateContainerExpirationPolicyMutation from '~/packages_and_registries/settings/project/graphql/mutations/update_container_expiration_policy.mutation.graphql';
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
import Tracking from '~/tracking';
import { GlCard, GlLoadingIcon } from '../../shared/stubs';
import { expirationPolicyPayload, expirationPolicyMutationPayload } from '../mock_data';
const localVue = createLocalVue();

View File

@ -1,5 +1,5 @@
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update';
import expirationPolicyQuery from '~/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql';
import { updateContainerExpirationPolicy } from '~/packages_and_registries/settings/project/graphql/utils/cache_update';
describe('Registry settings cache update', () => {
let client;

View File

@ -2,7 +2,7 @@ import {
formOptionsGenerator,
optionLabelGenerator,
olderThanTranslationGenerator,
} from '~/registry/settings/utils';
} from '~/packages_and_registries/settings/project/utils';
describe('Utils', () => {
describe('optionLabelGenerator', () => {

View File

@ -120,6 +120,7 @@ RSpec.describe Gitlab::Ci::Build::Policy::Changes do
context 'when branch is created' do
let_it_be(:project) { create(:project, :repository) }
let(:pipeline) do
create(:ci_empty_pipeline, project: project,
ref: 'feature',

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::File::Local do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:sha) { '12345' }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:params) { { local: location } }

View File

@ -6,6 +6,7 @@ RSpec.describe Gitlab::Ci::Config::External::File::Project do
let_it_be(:context_project) { create(:project) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:context_user) { user }
let(:parent_pipeline) { double(:parent_pipeline) }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::External::File::Template do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:context_params) { { project: project, sha: '12345', user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:template) { 'Auto-DevOps.gitlab-ci.yml' }

View File

@ -7,6 +7,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }

View File

@ -8,6 +8,7 @@ RSpec.describe Gitlab::Ci::Config::External::Processor do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:another_project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:sha) { '12345' }
let(:context_params) { { project: project, sha: sha, user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Build do
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:user) { create(:user, developer_projects: [project]) }
let(:pipeline) { Ci::Pipeline.new }
let(:variables_attributes) do

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::CancelPendingPipelines do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:prev_pipeline) { create(:ci_pipeline, project: project) }
let(:new_commit) { create(:commit, project: project) }
let(:pipeline) { create(:ci_pipeline, project: project, sha: new_commit.sha) }

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::TemplateUsage do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:command) do

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:pipeline) { build(:ci_empty_pipeline, user: user, project: project) }
let!(:step) { described_class.new(pipeline, command) }

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:pipeline) { build_stubbed(:ci_pipeline) }
let!(:step) { described_class.new(pipeline, command) }

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:head_sha) { project.repository.head_commit.id }
let(:pipeline) { build(:ci_empty_pipeline, project: project, sha: head_sha) }
let(:root_variables) { [] }
let(:seed_context) { double(pipeline: pipeline, root_variables: root_variables) }

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Seed::Deployment do
let_it_be(:project, refind: true) { create(:project, :repository) }
let(:pipeline) do
create(:ci_pipeline, project: project,
sha: 'b83d6e391c22777fca1ed3012fce84f633d7fed0')

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Seed::Environment do
let_it_be(:project) { create(:project) }
let(:job) { build(:ci_build, project: project) }
let(:seed) { described_class.new(job) }
let(:attributes) { {} }

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Seed::Processable::ResourceGroup do
let_it_be(:project) { create(:project) }
let(:job) { build(:ci_build, project: project) }
let(:seed) { described_class.new(job, resource_group_key) }

View File

@ -7,6 +7,7 @@ RSpec.describe Gitlab::Ci::Reports::TestFailureHistory, :aggregate_failures do
describe '#load!' do
let_it_be(:project) { create(:project) }
let(:failed_rspec) { create_test_case_rspec_failed }
let(:failed_java) { create_test_case_java_failed }

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe 'ci/syntax_templates' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:lint) { Gitlab::Ci::Lint.new(project: project, current_user: user) }
before do

View File

@ -6,6 +6,7 @@ RSpec.describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
include ChunkedIOHelpers
let_it_be(:build) { create(:ci_build, :running) }
let(:chunked_io) { described_class.new(build) }
before do

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state, factory_default: :keep do
let_it_be(:project) { create_default(:project).freeze }
let_it_be_with_reload(:build) { create(:ci_build, :success) }
let(:trace) { described_class.new(build) }
describe "associations" do

View File

@ -1158,8 +1158,17 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
describe ".system_usage_data_settings" do
let(:prometheus_client) { double(Gitlab::PrometheusClient) }
before do
allow(described_class).to receive(:operating_system).and_return('ubuntu-20.04')
expect(prometheus_client).to receive(:query).with(/gitlab_usage_ping:gitaly_apdex:ratio_avg_over_time_5m/).and_return([
{
'metric' => {},
'value' => [1616016381.473, '0.95']
}
])
expect(described_class).to receive(:with_prometheus_client).and_yield(prometheus_client)
end
subject { described_class.system_usage_data_settings }
@ -1171,6 +1180,10 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
it 'populates operating system information' do
expect(subject[:settings][:operating_system]).to eq('ubuntu-20.04')
end
it 'gathers gitaly apdex', :aggregate_failures do
expect(subject[:settings][:gitaly_apdex]).to be_within(0.001).of(0.95)
end
end
end

View File

@ -8,6 +8,7 @@ RSpec.describe Clusters::Agent do
it { is_expected.to belong_to(:created_by_user).class_name('User').optional }
it { is_expected.to belong_to(:project).class_name('::Project') }
it { is_expected.to have_many(:agent_tokens).class_name('Clusters::AgentToken') }
it { is_expected.to have_many(:last_used_agent_tokens).class_name('Clusters::AgentToken') }
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_length_of(:name).is_at_most(63) }

View File

@ -9,6 +9,19 @@ RSpec.describe Clusters::AgentToken do
it { is_expected.to validate_length_of(:name).is_at_most(255) }
it { is_expected.to validate_presence_of(:name) }
describe 'scopes' do
describe '.order_last_used_at_desc' do
let_it_be(:token_1) { create(:cluster_agent_token, last_used_at: 7.days.ago) }
let_it_be(:token_2) { create(:cluster_agent_token, last_used_at: nil) }
let_it_be(:token_3) { create(:cluster_agent_token, last_used_at: 2.days.ago) }
it 'sorts by last_used_at descending, with null values at last' do
expect(described_class.order_last_used_at_desc)
.to eq([token_3, token_1, token_2])
end
end
end
describe '#token' do
it 'is generated on save' do
agent_token = build(:cluster_agent_token, token_encrypted: nil)

View File

@ -18,6 +18,7 @@ RSpec.describe Packages::Dependency, type: :model do
let_it_be(:package_dependency1) { create(:packages_dependency, name: 'foo', version_pattern: '~1.0.0') }
let_it_be(:package_dependency2) { create(:packages_dependency, name: 'bar', version_pattern: '~2.5.0') }
let_it_be(:expected_ids) { [package_dependency1.id, package_dependency2.id] }
let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2) }
let(:chunk_size) { 50 }
let(:rows_limit) { 50 }
@ -40,6 +41,7 @@ RSpec.describe Packages::Dependency, type: :model do
context 'with a name bigger than column size' do
let_it_be(:big_name) { 'a' * (Packages::Dependency::MAX_STRING_LENGTH + 1) }
let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2).merge(big_name => '~1.0.0') }
it { is_expected.to match_array(expected_ids) }
@ -47,6 +49,7 @@ RSpec.describe Packages::Dependency, type: :model do
context 'with a version pattern bigger than column size' do
let_it_be(:big_version_pattern) { 'a' * (Packages::Dependency::MAX_STRING_LENGTH + 1) }
let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2).merge('test' => big_version_pattern) }
it { is_expected.to match_array(expected_ids) }
@ -65,6 +68,7 @@ RSpec.describe Packages::Dependency, type: :model do
let_it_be(:package_dependency5) { create(:packages_dependency, name: 'foo5', version_pattern: '~1.5.5') }
let_it_be(:package_dependency6) { create(:packages_dependency, name: 'foo6', version_pattern: '~1.5.6') }
let_it_be(:package_dependency7) { create(:packages_dependency, name: 'foo7', version_pattern: '~1.5.7') }
let(:expected_ids) { [package_dependency1.id, package_dependency2.id, package_dependency3.id, package_dependency4.id, package_dependency5.id, package_dependency6.id, package_dependency7.id] }
let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2, package_dependency3, package_dependency4, package_dependency5, package_dependency6, package_dependency7) }
@ -86,6 +90,7 @@ RSpec.describe Packages::Dependency, type: :model do
let_it_be(:package_dependency1) { create(:packages_dependency, name: 'foo', version_pattern: '~1.0.0') }
let_it_be(:package_dependency2) { create(:packages_dependency, name: 'bar', version_pattern: '~2.5.0') }
let_it_be(:expected_array) { [package_dependency1, package_dependency2] }
let(:names_and_version_patterns) { build_names_and_version_patterns(package_dependency1, package_dependency2) }
subject { Packages::Dependency.for_package_names_and_version_patterns(names_and_version_patterns) }

View File

@ -32,16 +32,19 @@ RSpec.describe Packages::Go::ModuleVersion, type: :model do
describe '#name' do
context 'with ref and name specified' do
let_it_be(:version) { create :go_module_version, mod: mod, name: 'foobar', commit: project.repository.head_commit, ref: project.repository.find_tag('v1.0.0') }
it('returns that name') { expect(version.name).to eq('foobar') }
end
context 'with ref specified and name unspecified' do
let_it_be(:version) { create :go_module_version, mod: mod, commit: project.repository.head_commit, ref: project.repository.find_tag('v1.0.0') }
it('returns the name of the ref') { expect(version.name).to eq('v1.0.0') }
end
context 'with ref and name unspecified' do
let_it_be(:version) { create :go_module_version, mod: mod, commit: project.repository.head_commit }
it('returns nil') { expect(version.name).to eq(nil) }
end
end
@ -49,11 +52,13 @@ RSpec.describe Packages::Go::ModuleVersion, type: :model do
describe '#gomod' do
context 'with go.mod missing' do
let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.0' }
it('returns nil') { expect(version.gomod).to eq(nil) }
end
context 'with go.mod present' do
let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.1' }
it('returns the contents of go.mod') { expect(version.gomod).to eq("module #{mod.name}\n") }
end
end
@ -62,6 +67,7 @@ RSpec.describe Packages::Go::ModuleVersion, type: :model do
context 'with a root module' do
context 'with an empty module path' do
let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.2' }
it_behaves_like '#files', 'all the files', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
end
end
@ -69,12 +75,14 @@ RSpec.describe Packages::Go::ModuleVersion, type: :model do
context 'with a root module and a submodule' do
context 'with an empty module path' do
let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
it_behaves_like '#files', 'files excluding the submodule', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
end
context 'with the submodule\'s path' do
let_it_be(:mod) { create :go_module, project: project, path: 'mod' }
let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
it_behaves_like '#files', 'the submodule\'s files', 'mod/go.mod', 'mod/a.go'
end
end
@ -84,6 +92,7 @@ RSpec.describe Packages::Go::ModuleVersion, type: :model do
context 'with a root module' do
context 'with an empty module path' do
let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.2' }
it_behaves_like '#archive', 'all the files', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
end
end
@ -91,12 +100,14 @@ RSpec.describe Packages::Go::ModuleVersion, type: :model do
context 'with a root module and a submodule' do
context 'with an empty module path' do
let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
it_behaves_like '#archive', 'files excluding the submodule', 'README.md', 'go.mod', 'a.go', 'pkg/b.go'
end
context 'with the submodule\'s path' do
let_it_be(:mod) { create :go_module, project: project, path: 'mod' }
let_it_be(:version) { create :go_module_version, :tagged, mod: mod, name: 'v1.0.3' }
it_behaves_like '#archive', 'the submodule\'s files', 'go.mod', 'a.go'
end
end

View File

@ -47,6 +47,7 @@ RSpec.describe Packages::Package, type: :model do
describe '.sort_by_attribute' do
let_it_be(:group) { create(:group, :public) }
let_it_be(:project) { create(:project, :public, namespace: group, name: 'project A') }
let!(:package1) { create(:npm_package, project: project, version: '3.1.0', name: "@#{project.root_namespace.path}/foo1") }
let!(:package2) { create(:nuget_package, project: project, version: '2.0.4') }
let(:package3) { create(:maven_package, project: project, version: '1.1.1', name: 'zzz') }
@ -896,6 +897,7 @@ RSpec.describe Packages::Package, type: :model do
let_it_be(:package_name) { 'composer-package-name' }
let_it_be(:json) { { 'name' => package_name } }
let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json } ) }
let!(:package) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
before do

View File

@ -41,6 +41,7 @@ RSpec.describe Packages::Tag, type: :model do
let_it_be(:tag1) { create(:packages_tag, package: package, name: 'tag1') }
let_it_be(:tag2) { create(:packages_tag, package: package, name: 'tag2') }
let_it_be(:tag3) { create(:packages_tag, package: package, name: 'tag3') }
let(:name) { 'tag1' }
subject { described_class.with_name(name) }

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Release do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository) }
let(:release) { create(:release, project: project, author: user) }
it { expect(release).to be_valid }

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Releases::Evidence do
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
describe 'associations' do

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Releases::Source do
let_it_be(:project) { create(:project, :repository, name: 'finance-cal') }
let(:tag_name) { 'v1.0' }
describe '.all' do

View File

@ -20,6 +20,7 @@ RSpec.describe AlertManagement::AlertPresenter do
end
let_it_be(:alert) { create(:alert_management_alert, project: project, payload: payload) }
let(:alert_url) { "http://localhost/#{project.full_path}/-/alert_management/#{alert.iid}/details" }
subject(:presenter) { described_class.new(alert) }

View File

@ -8,6 +8,7 @@ RSpec.describe Ci::PipelinePresenter do
let_it_be(:user) { create(:user) }
let_it_be_with_reload(:project) { create(:project, :test_repo) }
let_it_be_with_reload(:pipeline) { create(:ci_pipeline, project: project) }
let(:current_user) { user }
subject(:presenter) do
@ -246,6 +247,7 @@ RSpec.describe Ci::PipelinePresenter do
context 'permissions' do
let_it_be_with_refind(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project) }
let(:pipeline) { merge_request.all_pipelines.take }
shared_examples 'private merge requests' do

View File

@ -7,6 +7,7 @@ RSpec.describe LabelPresenter do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let(:label) { build_stubbed(:label, project: project).present(issuable_subject: project) }
let(:group_label) { build_stubbed(:group_label, group: group).present(issuable_subject: project) }

View File

@ -9,6 +9,7 @@ RSpec.describe ::Packages::Composer::PackagesPresenter do
let_it_be(:json) { { 'name' => package_name } }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :custom_repo, files: { 'composer.json' => json.to_json }, group: group) }
let!(:package1) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '1.0.0', json: json) }
let!(:package2) { create(:composer_package, :with_metadatum, project: project, name: package_name, version: '2.0.0', json: json) }

View File

@ -7,6 +7,7 @@ RSpec.describe ::Packages::Conan::PackagePresenter do
let_it_be(:package) { create(:conan_package) }
let_it_be(:project) { package.project }
let_it_be(:conan_package_reference) { '123456789'}
let(:params) { { package_scope: :instance } }
shared_examples 'no existing package' do

View File

@ -125,6 +125,7 @@ RSpec.describe ::Packages::Detail::PackagePresenter do
context 'with nuget_metadatum' do
let_it_be(:package) { create(:nuget_package, project: project) }
let_it_be(:nuget_metadatum) { create(:nuget_metadatum, package: package) }
let(:expected_package_details) { super().merge(nuget_metadatum: nuget_metadatum) }
it 'returns nuget_metadatum' do

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe ::Packages::Npm::PackagePresenter do
let_it_be(:project) { create(:project) }
let_it_be(:package_name) { "@#{project.root_namespace.path}/test" }
let!(:package1) { create(:npm_package, version: '1.0.4', project: project, name: package_name) }
let!(:package2) { create(:npm_package, version: '1.0.6', project: project, name: package_name) }
let!(:latest_package) { create(:npm_package, version: '1.0.11', project: project, name: package_name) }

View File

@ -11,6 +11,7 @@ RSpec.describe Packages::Nuget::SearchResultsPresenter do
let_it_be(:packages_c) { create_list(:nuget_package, 5, project: project, name: 'DummyPackageC') }
let_it_be(:search_results) { OpenStruct.new(total_count: 3, results: [package_a, packages_b, packages_c].flatten) }
let_it_be(:presenter) { described_class.new(search_results) }
let(:total_count) { presenter.total_count }
let(:data) { presenter.data }

Some files were not shown because too many files have changed in this diff Show More