Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
95671fac6e
commit
fde3e0435c
|
@ -276,6 +276,12 @@ GitlabSecurity/PublicSend:
|
|||
Gitlab/DuplicateSpecLocation:
|
||||
Enabled: true
|
||||
|
||||
Gitlab/PolicyRuleBoolean:
|
||||
Enabled: true
|
||||
Include:
|
||||
- 'app/policies/**/*'
|
||||
- 'ee/app/policies/**/*'
|
||||
|
||||
Cop/InjectEnterpriseEditionModule:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
|
|
|
@ -41,6 +41,10 @@ Graphql/ResolverType:
|
|||
- 'app/graphql/resolvers/users/group_count_resolver.rb'
|
||||
- 'ee/app/graphql/resolvers/vulnerabilities_base_resolver.rb'
|
||||
|
||||
Gitlab/PolicyRuleBoolean:
|
||||
Exclude:
|
||||
- 'ee/app/policies/ee/identity_provider_policy.rb'
|
||||
|
||||
Rails/SaveBang:
|
||||
Exclude:
|
||||
- 'ee/spec/controllers/projects/merge_requests_controller_spec.rb'
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
import { __ } from '~/locale';
|
||||
|
||||
export default (IssuableTokenKeys, disableTargetBranchFilter = false) => {
|
||||
const reviewerToken = {
|
||||
formattedKey: __('Reviewer'),
|
||||
key: 'reviewer',
|
||||
type: 'string',
|
||||
param: 'username',
|
||||
symbol: '@',
|
||||
icon: 'user',
|
||||
tag: '@reviewer',
|
||||
};
|
||||
IssuableTokenKeys.tokenKeys.splice(2, 0, reviewerToken);
|
||||
IssuableTokenKeys.tokenKeysWithAlternative.splice(2, 0, reviewerToken);
|
||||
|
||||
const draftToken = {
|
||||
token: {
|
||||
formattedKey: __('Draft'),
|
||||
|
|
|
@ -21,15 +21,6 @@ export const tokenKeys = [
|
|||
icon: 'user',
|
||||
tag: '@assignee',
|
||||
},
|
||||
{
|
||||
formattedKey: __('Reviewer'),
|
||||
key: 'reviewer',
|
||||
type: 'string',
|
||||
param: 'username',
|
||||
symbol: '@',
|
||||
icon: 'user',
|
||||
tag: '@reviewer',
|
||||
},
|
||||
{
|
||||
formattedKey: __('Milestone'),
|
||||
key: 'milestone',
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<script>
|
||||
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
},
|
||||
props: {
|
||||
visibilityLevelOptions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
defaultLevel: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedOption: this.getDefaultOption(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getDefaultOption() {
|
||||
return this.visibilityLevelOptions.find(option => option.level === this.defaultLevel);
|
||||
},
|
||||
onClick(option) {
|
||||
this.selectedOption = option;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<input type="hidden" name="group[visibility_level]" :value="selectedOption.level" />
|
||||
<gl-dropdown :text="selectedOption.label" class="gl-w-full" menu-class="gl-w-full! gl-mb-0">
|
||||
<gl-dropdown-item
|
||||
v-for="option in visibilityLevelOptions"
|
||||
:key="option.level"
|
||||
:secondary-text="option.description"
|
||||
@click="onClick(option)"
|
||||
>
|
||||
<div class="gl-font-weight-bold gl-mb-1">{{ option.label }}</div>
|
||||
</gl-dropdown-item>
|
||||
</gl-dropdown>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,24 @@
|
|||
import Vue from 'vue';
|
||||
import VisibilityLevelDropdown from './components/visibility_level_dropdown.vue';
|
||||
|
||||
export default () => {
|
||||
const el = document.querySelector('.js-visibility-level-dropdown');
|
||||
|
||||
if (!el) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { visibilityLevelOptions, defaultLevel } = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
render(createElement) {
|
||||
return createElement(VisibilityLevelDropdown, {
|
||||
props: {
|
||||
visibilityLevelOptions: JSON.parse(visibilityLevelOptions),
|
||||
defaultLevel: Number(defaultLevel),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
|
||||
#import "../fragments/container_repository.fragment.graphql"
|
||||
|
||||
query getProjectContainerRepositories(
|
||||
query getGroupContainerRepositories(
|
||||
$fullPath: ID!
|
||||
$name: String
|
||||
$first: Int
|
|
@ -12,8 +12,8 @@ import TagsList from '../components/details_page/tags_list.vue';
|
|||
import TagsLoader from '../components/details_page/tags_loader.vue';
|
||||
import EmptyTagsState from '../components/details_page/empty_tags_state.vue';
|
||||
|
||||
import getContainerRepositoryDetailsQuery from '../graphql/queries/get_container_repository_details.graphql';
|
||||
import deleteContainerRepositoryTagsMutation from '../graphql/mutations/delete_container_repository_tags.graphql';
|
||||
import getContainerRepositoryDetailsQuery from '../graphql/queries/get_container_repository_details.query.graphql';
|
||||
import deleteContainerRepositoryTagsMutation from '../graphql/mutations/delete_container_repository_tags.mutation.graphql';
|
||||
|
||||
import {
|
||||
ALERT_SUCCESS_TAG,
|
||||
|
|
|
@ -18,9 +18,9 @@ import RegistryHeader from '../components/list_page/registry_header.vue';
|
|||
import ImageList from '../components/list_page/image_list.vue';
|
||||
import CliCommands from '../components/list_page/cli_commands.vue';
|
||||
|
||||
import getProjectContainerRepositories from '../graphql/queries/get_project_container_repositories.graphql';
|
||||
import getGroupContainerRepositories from '../graphql/queries/get_group_container_repositories.graphql';
|
||||
import deleteContainerRepository from '../graphql/mutations/delete_container_repository.graphql';
|
||||
import getProjectContainerRepositoriesQuery from '../graphql/queries/get_project_container_repositories.query.graphql';
|
||||
import getGroupContainerRepositoriesQuery from '../graphql/queries/get_group_container_repositories.query.graphql';
|
||||
import deleteContainerRepositoryMutation from '../graphql/mutations/delete_container_repository.mutation.graphql';
|
||||
|
||||
import {
|
||||
DELETE_IMAGE_SUCCESS_MESSAGE,
|
||||
|
@ -111,8 +111,8 @@ export default {
|
|||
},
|
||||
graphQlQuery() {
|
||||
return this.config.isGroupPage
|
||||
? getGroupContainerRepositories
|
||||
: getProjectContainerRepositories;
|
||||
? getGroupContainerRepositoriesQuery
|
||||
: getProjectContainerRepositoriesQuery;
|
||||
},
|
||||
queryVariables() {
|
||||
return {
|
||||
|
@ -152,7 +152,7 @@ export default {
|
|||
this.mutationLoading = true;
|
||||
return this.$apollo
|
||||
.mutate({
|
||||
mutation: deleteContainerRepository,
|
||||
mutation: deleteContainerRepositoryMutation,
|
||||
variables: {
|
||||
id: this.itemToDelete.id,
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { isEqual, get, isEmpty } from 'lodash';
|
||||
import expirationPolicyQuery from '../graphql/queries/get_expiration_policy.graphql';
|
||||
import expirationPolicyQuery from '../graphql/queries/get_expiration_policy.query.graphql';
|
||||
import {
|
||||
FETCH_SETTINGS_ERROR_MESSAGE,
|
||||
UNAVAILABLE_FEATURE_TITLE,
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
EXPIRATION_POLICY_FOOTER_NOTE,
|
||||
} from '~/registry/settings/constants';
|
||||
import { formOptionsGenerator } from '~/registry/settings/utils';
|
||||
import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.graphql';
|
||||
import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.mutation.graphql';
|
||||
import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update';
|
||||
import ExpirationDropdown from './expiration_dropdown.vue';
|
||||
import ExpirationInput from './expiration_input.vue';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { produce } from 'immer';
|
||||
import expirationPolicyQuery from '../queries/get_expiration_policy.graphql';
|
||||
import expirationPolicyQuery from '../queries/get_expiration_policy.query.graphql';
|
||||
|
||||
export const updateContainerExpirationPolicy = projectPath => (client, { data: updatedData }) => {
|
||||
const queryAndParams = {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
<script>
|
||||
import { GlButton, GlIcon, GlLink, GlPopover } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlPopover,
|
||||
},
|
||||
props: {
|
||||
helpPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
discoverProjectSecurityPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
securityReportsHelp: s__('SecurityReports|Security reports help page link'),
|
||||
upgradeToManageVulnerabilities: s__('SecurityReports|Upgrade to manage vulnerabilities'),
|
||||
upgradeToInteract: s__(
|
||||
'SecurityReports|Upgrade to interact, track and shift left with vulnerability management features in the UI.',
|
||||
),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="discoverProjectSecurityPath">
|
||||
<gl-button
|
||||
ref="discoverProjectSecurity"
|
||||
icon="information-o"
|
||||
category="tertiary"
|
||||
:aria-label="$options.i18n.upgradeToManageVulnerabilities"
|
||||
/>
|
||||
|
||||
<gl-popover
|
||||
:target="() => $refs.discoverProjectSecurity.$el"
|
||||
:title="$options.i18n.upgradeToManageVulnerabilities"
|
||||
placement="top"
|
||||
triggers="click blur"
|
||||
>
|
||||
{{ $options.i18n.upgradeToInteract }}
|
||||
<gl-link :href="discoverProjectSecurityPath" target="_blank" class="gl-font-sm">{{
|
||||
__('Learn more')
|
||||
}}</gl-link>
|
||||
</gl-popover>
|
||||
</span>
|
||||
|
||||
<gl-link v-else target="_blank" :href="helpPath" :aria-label="$options.i18n.securityReportsHelp">
|
||||
<gl-icon name="question" />
|
||||
</gl-link>
|
||||
</template>
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import { GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import ReportSection from '~/reports/components/report_section.vue';
|
||||
import { LOADING, ERROR, SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR } from '~/reports/constants';
|
||||
|
@ -8,6 +8,7 @@ import { s__ } from '~/locale';
|
|||
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
|
||||
import createFlash from '~/flash';
|
||||
import Api from '~/api';
|
||||
import HelpIcon from './components/help_icon.vue';
|
||||
import SecurityReportDownloadDropdown from './components/security_report_download_dropdown.vue';
|
||||
import SecuritySummary from './components/security_summary.vue';
|
||||
import store from './store';
|
||||
|
@ -23,10 +24,10 @@ import { extractSecurityReportArtifacts } from './utils';
|
|||
export default {
|
||||
store,
|
||||
components: {
|
||||
GlIcon,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
ReportSection,
|
||||
HelpIcon,
|
||||
SecurityReportDownloadDropdown,
|
||||
SecuritySummary,
|
||||
},
|
||||
|
@ -44,6 +45,11 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
discoverProjectSecurityPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
sastComparisonPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -64,6 +70,11 @@ export default {
|
|||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
canDiscoverProjectSecurity: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -231,7 +242,6 @@ export default {
|
|||
downloadFromPipelineTab: s__(
|
||||
'SecurityReports|Go to the %{linkStart}pipelines tab%{linkEnd} to download the security reports',
|
||||
),
|
||||
securityReportsHelp: s__('SecurityReports|Security reports help page link'),
|
||||
},
|
||||
summarySlots: [SLOT_SUCCESS, SLOT_LOADING, SLOT_ERROR],
|
||||
};
|
||||
|
@ -248,14 +258,10 @@ export default {
|
|||
<span :key="slot">
|
||||
<security-summary :message="groupedSummaryText" />
|
||||
|
||||
<gl-link
|
||||
target="_blank"
|
||||
data-testid="help"
|
||||
:href="securityReportsDocsPath"
|
||||
:aria-label="$options.i18n.securityReportsHelp"
|
||||
>
|
||||
<gl-icon name="question" />
|
||||
</gl-link>
|
||||
<help-icon
|
||||
:help-path="securityReportsDocsPath"
|
||||
:discover-project-security-path="discoverProjectSecurityPath"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
@ -300,14 +306,10 @@ export default {
|
|||
</template>
|
||||
</gl-sprintf>
|
||||
|
||||
<gl-link
|
||||
target="_blank"
|
||||
data-testid="help"
|
||||
:href="securityReportsDocsPath"
|
||||
:aria-label="$options.i18n.securityReportsHelp"
|
||||
>
|
||||
<gl-icon name="question" />
|
||||
</gl-link>
|
||||
<help-icon
|
||||
:help-path="securityReportsDocsPath"
|
||||
:discover-project-security-path="discoverProjectSecurityPath"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="canShowDownloads" #action-buttons>
|
||||
|
|
|
@ -157,6 +157,16 @@ module VisibilityLevelHelper
|
|||
end
|
||||
end
|
||||
|
||||
def visibility_level_options(form_model)
|
||||
available_visibility_levels(form_model).map do |level|
|
||||
{
|
||||
level: level,
|
||||
label: visibility_level_label(level),
|
||||
description: visibility_level_description(level, form_model)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def snippets_selected_visibility_level(visibility_levels, selected)
|
||||
visibility_levels.find { |level| level == selected } || visibility_levels.min
|
||||
end
|
||||
|
|
|
@ -12,10 +12,16 @@ module Timebox
|
|||
include FromUnion
|
||||
|
||||
TimeboxStruct = Struct.new(:title, :name, :id) do
|
||||
include GlobalID::Identification
|
||||
|
||||
# Ensure these models match the interface required for exporting
|
||||
def serializable_hash(_opts = {})
|
||||
{ title: title, name: name, id: id }
|
||||
end
|
||||
|
||||
def self.declarative_policy_class
|
||||
"TimeboxPolicy"
|
||||
end
|
||||
end
|
||||
|
||||
# Represents a "No Timebox" state used for filtering Issues and Merge
|
||||
|
@ -24,8 +30,6 @@ module Timebox
|
|||
Any = TimeboxStruct.new('Any Timebox', '', -1)
|
||||
Upcoming = TimeboxStruct.new('Upcoming', '#upcoming', -2)
|
||||
Started = TimeboxStruct.new('Started', '#started', -3)
|
||||
# For Iteration
|
||||
Current = TimeboxStruct.new('Current', '#current', -4)
|
||||
|
||||
included do
|
||||
# Defines the same constants above, but inside the including class.
|
||||
|
@ -33,7 +37,6 @@ module Timebox
|
|||
const_set :Any, TimeboxStruct.new("Any #{self.name}", '', -1)
|
||||
const_set :Upcoming, TimeboxStruct.new('Upcoming', '#upcoming', -2)
|
||||
const_set :Started, TimeboxStruct.new('Started', '#started', -3)
|
||||
const_set :Current, TimeboxStruct.new('Current', '#current', -4)
|
||||
|
||||
alias_method :timebox_id, :id
|
||||
|
||||
|
|
|
@ -9,6 +9,10 @@ class Milestone < ApplicationRecord
|
|||
|
||||
prepend_if_ee('::EE::Milestone') # rubocop: disable Cop/InjectEnterpriseEditionModule
|
||||
|
||||
class Predefined
|
||||
ALL = [::Timebox::None, ::Timebox::Any, ::Timebox::Started, ::Timebox::Upcoming].freeze
|
||||
end
|
||||
|
||||
has_many :milestone_releases
|
||||
has_many :releases, through: :milestone_releases
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TimeboxPolicy < BasePolicy
|
||||
# stub permissions policy on None, Any, Upcoming, Started and Current timeboxes
|
||||
|
||||
rule { default }.policy do
|
||||
enable :read_iteration
|
||||
enable :read_milestone
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
= f.label :visibility_level, class: 'label-bold' do
|
||||
= _('Visibility level')
|
||||
.js-visibility-level-dropdown{ data: { visibility_level_options: visibility_level_options(@group).to_json, default_level: f.object.visibility_level } }
|
|
@ -13,6 +13,10 @@ class GitlabUsagePingWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
sidekiq_retry_in { |count| (count + 1) * 8.hours.to_i }
|
||||
|
||||
def perform
|
||||
# Disable usage ping for GitLab.com
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/292929 for details
|
||||
return if Gitlab.com?
|
||||
|
||||
# Multiple Sidekiq workers could run this. We should only do this at most once a day.
|
||||
in_lock(LEASE_KEY, ttl: LEASE_TIMEOUT) do
|
||||
# Splay the request over a minute to avoid thundering herd problems.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Show upgrade popover in security widget in merge requests when the user is able to upgrade
|
||||
merge_request: 49613
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add ability to aggregated metrics in Usage Ping
|
||||
merge_request: 49886
|
||||
author:
|
||||
type: added
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: product_analytics_aggregated_metrics
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44624
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267550
|
||||
milestone: '13.6'
|
||||
type: development
|
||||
group: group::product analytics
|
||||
default_enabled: false
|
|
@ -1294,6 +1294,11 @@ type Board {
|
|||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
The board iteration.
|
||||
"""
|
||||
iteration: Iteration
|
||||
|
||||
"""
|
||||
Labels of the board
|
||||
"""
|
||||
|
@ -23339,6 +23344,11 @@ input UpdateBoardInput {
|
|||
"""
|
||||
id: BoardID!
|
||||
|
||||
"""
|
||||
The ID of iteration to be assigned to the board.
|
||||
"""
|
||||
iterationId: IterationID
|
||||
|
||||
"""
|
||||
The IDs of labels to be added to the board
|
||||
"""
|
||||
|
@ -24901,6 +24911,11 @@ type Vulnerability implements Noteable {
|
|||
last: Int
|
||||
): VulnerabilityExternalIssueLinkConnection!
|
||||
|
||||
"""
|
||||
Indicates whether there is a solution available for this vulnerability.
|
||||
"""
|
||||
hasSolutions: Boolean
|
||||
|
||||
"""
|
||||
GraphQL ID of the vulnerability
|
||||
"""
|
||||
|
|
|
@ -3449,6 +3449,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "iteration",
|
||||
"description": "The board iteration.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "Iteration",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "labels",
|
||||
"description": "Labels of the board",
|
||||
|
@ -68291,6 +68305,16 @@
|
|||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "iterationId",
|
||||
"description": "The ID of iteration to be assigned to the board.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "IterationID",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "weight",
|
||||
"description": "The weight value to be assigned to the board",
|
||||
|
@ -72465,6 +72489,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "hasSolutions",
|
||||
"description": "Indicates whether there is a solution available for this vulnerability.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "GraphQL ID of the vulnerability",
|
||||
|
|
|
@ -247,6 +247,7 @@ Represents a project or group board.
|
|||
| `hideBacklogList` | Boolean | Whether or not backlog list is hidden |
|
||||
| `hideClosedList` | Boolean | Whether or not closed list is hidden |
|
||||
| `id` | ID! | ID (global ID) of the board |
|
||||
| `iteration` | Iteration | The board iteration. |
|
||||
| `labels` | LabelConnection | Labels of the board |
|
||||
| `lists` | BoardListConnection | Lists of the board |
|
||||
| `milestone` | Milestone | The board milestone |
|
||||
|
@ -3747,6 +3748,7 @@ Represents a vulnerability.
|
|||
| `discussions` | DiscussionConnection! | All discussions on this noteable |
|
||||
| `dismissedAt` | Time | Timestamp of when the vulnerability state was changed to dismissed |
|
||||
| `externalIssueLinks` | VulnerabilityExternalIssueLinkConnection! | List of external issue links related to the vulnerability |
|
||||
| `hasSolutions` | Boolean | Indicates whether there is a solution available for this vulnerability. |
|
||||
| `id` | ID! | GraphQL ID of the vulnerability |
|
||||
| `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerability. |
|
||||
| `issueLinks` | VulnerabilityIssueLinkConnection! | List of issue links related to the vulnerability |
|
||||
|
|
|
@ -526,21 +526,28 @@ You can use the following fake tokens as examples:
|
|||
### Usage list
|
||||
<!-- vale off -->
|
||||
|
||||
| Usage | Guidance | [Vale](../testing.md#vale) Tests |
|
||||
|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|
|
||||
| currently | Do not use when talking about the product or its features. The documentation describes the product as it is today. | None |
|
||||
| e.g., i.e., via | Do not use Latin abbreviations.<br><br>- Instead of **e.g.**, use **for example**, **such as**, **for instance**, or **like**.<br>- Instead of **i.e.**, use **that is**.<br>- Instead of **via**, use **with**, **through**, or **by using**. | [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml) |
|
||||
| future tense | When possible, use present tense instead. For example, use `after you execute this command, GitLab displays the result` instead of `after you execute this command, GitLab will display the result`. | [`FutureTense.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FutureTense.yml) |
|
||||
| high availability, HA | Do not use. Direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md) for information about configuring GitLab to have the performance needed for additional users over time. | None |
|
||||
| I, me | Do not use first-person singular. Use **you**, **we**, or **us** instead. | [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml) |
|
||||
| jargon | Do not use. Define the term or [link to a definition](#links-to-external-documentation). | None |
|
||||
| may, might | **Might** means something has the probability of occurring. **May** gives permission to do something. Consider **can** instead of **may**. | None |
|
||||
| please | Do not use. For details, see the [Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/p/please). | None |
|
||||
| profanity | Do not use. Doing so may negatively affect other users and contributors, which is contrary to the GitLab value of [Diversity, Inclusion, and Belonging](https://about.gitlab.com/handbook/values/#diversity-inclusion). | None |
|
||||
| scalability | Do not use when talking about increasing GitLab performance for additional users. The words scale or scaling are sometimes acceptable, but references to increasing GitLab performance for additional users should direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md) page. | None |
|
||||
| simply, easily, handy, useful | Do not use. If the user doesn't find the process to be these things, we lose their trust. | None |
|
||||
| slashes | Instead of **and/or** use **or** or another sensible construction. This rule applies to other slashes as well, like **follow/unfollow**. Exception like **CI/CD** are allowed. | None |
|
||||
| that | Do not use. Example: `the file that you save` can be `the file you save`. | None |
|
||||
| {::nomarkdown}<div style="width:140px">Usage</div>{:/} | Guidance |
|
||||
|-----------------------|-----|
|
||||
| and/or | Use **or** instead, or another sensible construction. |
|
||||
| currently | Do not use when talking about the product or its features. The documentation describes the product as it is today. |
|
||||
| easily | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
|
||||
| e.g. | Do not use Latin abbreviations. Use **for example**, **such as**, **for instance**, or **like** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) |
|
||||
| future tense | When possible, use present tense instead. For example, use `after you execute this command, GitLab displays the result` instead of `after you execute this command, GitLab will display the result`. ([Vale](../testing.md#vale) rule: [`FutureTense.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FutureTense.yml)) |
|
||||
| handy | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
|
||||
| high availability, HA | Do not use. Instead, direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md) for information about configuring GitLab for handling greater amounts of users. |
|
||||
| I | Do not use first-person singular. Use **you**, **we**, or **us** instead. ([Vale](../testing.md#vale) rule: [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml)) |
|
||||
| i.e. | Do not use Latin abbreviations. Use **that is** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) |
|
||||
| jargon | Do not use. Define the term or [link to a definition](#links-to-external-documentation). |
|
||||
| may, might | **Might** means something has the probability of occurring. **May** gives permission to do something. Consider **can** instead of **may**. |
|
||||
| me | Do not use first-person singular. Use **you**, **we**, or **us** instead. ([Vale](../testing.md#vale) rule: [`FirstPerson.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FirstPerson.yml)) |
|
||||
| please | Do not use. For details, see the [Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/p/please). |
|
||||
| profanity | Do not use. Doing so may negatively affect other users and contributors, which is contrary to the GitLab value of [Diversity, Inclusion, and Belonging](https://about.gitlab.com/handbook/values/#diversity-inclusion). |
|
||||
| scalability | Do not use when talking about increasing GitLab performance for additional users. The words scale or scaling are sometimes acceptable, but references to increasing GitLab performance for additional users should direct readers to the GitLab [reference architectures](../../../administration/reference_architectures/index.md) page. |
|
||||
| simply | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
|
||||
| slashes | Instead of **and/or**, use **or** or another sensible construction. This rule also applies to other slashes, like **follow/unfollow**. Some exceptions (like **CI/CD**) are allowed. |
|
||||
| that | Do not use. Example: `the file that you save` can be `the file you save`. |
|
||||
| useful | Do not use. If the user doesn't find the process to be these things, we lose their trust. |
|
||||
| via | Do not use Latin abbreviations. Use **with**, **through**, or **by using** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) |
|
||||
|
||||
<!-- vale on -->
|
||||
### Contractions
|
||||
|
|
|
@ -178,6 +178,29 @@ if Feature.disabled?(:my_feature_flag, project, default_enabled: true)
|
|||
end
|
||||
```
|
||||
|
||||
If not specified, `default_enabled` is `false`.
|
||||
|
||||
To force reading the `default_enabled` value from the relative YAML definition file, use
|
||||
`default_enabled: :yaml`:
|
||||
|
||||
```ruby
|
||||
if Feature.enabled?(:feature_flag, project, default_enabled: :yaml)
|
||||
# execute code if feature flag is enabled
|
||||
end
|
||||
```
|
||||
|
||||
```ruby
|
||||
if Feature.disabled?(:feature_flag, project, default_enabled: :yaml)
|
||||
# execute code if feature flag is disabled
|
||||
end
|
||||
```
|
||||
|
||||
This allows to use the same feature flag check across various parts of the codebase and
|
||||
maintain the status of `default_enabled` in the YAML definition file which is the SSOT.
|
||||
|
||||
If `default_enabled: :yaml` is used, a YAML definition is expected or an error is raised
|
||||
in development or test environment, while returning `false` on production.
|
||||
|
||||
If not specified, the default feature flag type for `Feature.enabled?` and `Feature.disabled?`
|
||||
is `type: development`. For all other feature flag types, you must specify the `type:`:
|
||||
|
||||
|
|
|
@ -63,10 +63,20 @@ end
|
|||
Within the rule DSL, you can use:
|
||||
|
||||
- A regular word mentions a condition by name - a rule that is in effect when that condition is truthy.
|
||||
- `~` indicates negation.
|
||||
- `~` indicates negation, also available as `negate`.
|
||||
- `&` and `|` are logical combinations, also available as `all?(...)` and `any?(...)`.
|
||||
- `can?(:other_ability)` delegates to the rules that apply to `:other_ability`. Note that this is distinct from the instance method `can?`, which can check dynamically - this only configures a delegation to another ability.
|
||||
|
||||
`~`, `&` and `|` operators are overridden methods in
|
||||
[`DeclarativePolicy::Rule::Base`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/declarative_policy/rule.rb).
|
||||
|
||||
Do not use boolean operators such as `&&` and `||` within the rule DSL,
|
||||
as conditions within rule blocks are objects, not booleans. The same
|
||||
applies for ternary operators (`condition ? ... : ...`), and `if`
|
||||
blocks. These operators cannot be overridden, and are hence banned via a
|
||||
[custom
|
||||
cop](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49771).
|
||||
|
||||
## Scores, Order, Performance
|
||||
|
||||
To see how the rules get evaluated into a judgment, it is useful in a console to use `policy.debug(:some_ability)`. This prints the rules in the order they are evaluated.
|
||||
|
|
|
@ -25,19 +25,6 @@ want to have more specific search results.
|
|||
|
||||
Advanced Search only supports searching the [default branch](../project/repository/branches/index.md#default-branch).
|
||||
|
||||
## Use cases
|
||||
|
||||
Let's say for example that the product you develop relies on the code of another
|
||||
product that's hosted under some other group.
|
||||
|
||||
Since under your GitLab instance there are hosted hundreds of different projects,
|
||||
you need the search results to be as efficient as possible. You have a feeling
|
||||
of what you want to find (e.g., a function name), but at the same you're also
|
||||
not so sure.
|
||||
|
||||
In that case, using the advanced search syntax in your query will yield much
|
||||
better results.
|
||||
|
||||
## Using the Advanced Search Syntax
|
||||
|
||||
The Advanced Search Syntax supports fuzzy or exact search queries with prefixes,
|
||||
|
@ -93,3 +80,10 @@ Examples:
|
|||
- Finding `success` in all files excluding `.po|pot` files: [`success -filename:*.po*`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=success+-filename%3A*.po*&group_id=9970&project_id=278964)
|
||||
- Finding `import` excluding minified JavaScript (`.min.js`) files: [`import -extension:min.js`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=import+-extension%3Amin.js&group_id=9970&project_id=278964)
|
||||
- Finding `docs` for all files outside the `docs/` folder: [`docs -path:docs/`](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=blobs&repository_ref=&search=docs+-path%3Adocs%2F&group_id=9970&project_id=278964)
|
||||
|
||||
### Search by issue or merge request ID
|
||||
|
||||
You can search a specific issue or merge request by its ID with a special prefix.
|
||||
|
||||
- To search by issue ID, use prefix `#` followed by issue ID. For example, [#23456](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=issues&repository_ref=&search=%2323456&group_id=9970&project_id=278964)
|
||||
- To search by merge request ID, use prefix `!` followed by merge request ID. For example [!23456](https://gitlab.com/search?utf8=%E2%9C%93&snippets=&scope=merge_requests&repository_ref=&search=%2123456&group_id=9970&project_id=278964)
|
||||
|
|
|
@ -68,6 +68,9 @@ class Feature
|
|||
Feature::Definition.valid_usage!(key, type: type, default_enabled: default_enabled)
|
||||
end
|
||||
|
||||
# If `default_enabled: :yaml` we fetch the value from the YAML definition instead.
|
||||
default_enabled = Feature::Definition.default_enabled?(key) if default_enabled == :yaml
|
||||
|
||||
# During setup the database does not exist yet. So we haven't stored a value
|
||||
# for the feature yet and return the default.
|
||||
return default_enabled unless Gitlab::Database.exists?
|
||||
|
|
|
@ -71,9 +71,7 @@ class Feature
|
|||
"a valid syntax: #{TYPES.dig(type, :example)}"
|
||||
end
|
||||
|
||||
# We accept an array of defaults as some features are undefined
|
||||
# and have `default_enabled: true/false`
|
||||
unless Array(default_enabled).include?(default_enabled_in_code)
|
||||
unless default_enabled_in_code == :yaml || default_enabled == default_enabled_in_code
|
||||
# Raise exception in test and dev
|
||||
raise Feature::InvalidFeatureFlagError, "The `default_enabled:` of `#{key}` is not equal to config: " \
|
||||
"#{default_enabled_in_code} vs #{default_enabled}. Ensure to update #{path}"
|
||||
|
@ -96,6 +94,10 @@ class Feature
|
|||
@definitions ||= load_all!
|
||||
end
|
||||
|
||||
def get(key)
|
||||
definitions[key.to_sym]
|
||||
end
|
||||
|
||||
def reload!
|
||||
@definitions = load_all!
|
||||
end
|
||||
|
@ -105,7 +107,7 @@ class Feature
|
|||
end
|
||||
|
||||
def valid_usage!(key, type:, default_enabled:)
|
||||
if definition = definitions[key.to_sym]
|
||||
if definition = get(key)
|
||||
definition.valid_usage!(type_in_code: type, default_enabled_in_code: default_enabled)
|
||||
elsif type_definition = self::TYPES[type]
|
||||
raise InvalidFeatureFlagError, "Missing feature definition for `#{key}`" unless type_definition[:optional]
|
||||
|
@ -114,6 +116,17 @@ class Feature
|
|||
end
|
||||
end
|
||||
|
||||
def default_enabled?(key)
|
||||
if definition = get(key)
|
||||
definition.default_enabled
|
||||
else
|
||||
Gitlab::ErrorTracking.track_and_raise_for_dev_exception(
|
||||
InvalidFeatureFlagError.new("The feature flag YAML definition for '#{key}' does not exist"))
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def register_hot_reloader!
|
||||
# Reload feature flags on change of this file or any `.yml`
|
||||
file_watcher = Rails.configuration.file_watcher.new(reload_files, reload_directories) do
|
||||
|
|
|
@ -12,7 +12,7 @@ module Gitlab
|
|||
:seeds_block, :variables_attributes, :push_options,
|
||||
:chat_data, :allow_mirror_update, :bridge, :content, :dry_run,
|
||||
# These attributes are set by Chains during processing:
|
||||
:config_content, :yaml_processor_result, :stage_seeds
|
||||
:config_content, :yaml_processor_result, :pipeline_seed
|
||||
) do
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
|
|
|
@ -10,12 +10,12 @@ module Gitlab
|
|||
PopulateError = Class.new(StandardError)
|
||||
|
||||
def perform!
|
||||
raise ArgumentError, 'missing stage seeds' unless @command.stage_seeds
|
||||
raise ArgumentError, 'missing pipeline seed' unless @command.pipeline_seed
|
||||
|
||||
##
|
||||
# Populate pipeline with all stages, and stages with builds.
|
||||
#
|
||||
pipeline.stages = @command.stage_seeds.map(&:to_resource)
|
||||
pipeline.stages = @command.pipeline_seed.stages
|
||||
|
||||
if stage_names.empty?
|
||||
return error('No stages / jobs for this pipeline.')
|
||||
|
|
|
@ -29,11 +29,11 @@ module Gitlab
|
|||
##
|
||||
# Gather all runtime build/stage errors
|
||||
#
|
||||
if stage_seeds_errors
|
||||
return error(stage_seeds_errors.join("\n"), config_error: true)
|
||||
if pipeline_seed.errors
|
||||
return error(pipeline_seed.errors.join("\n"), config_error: true)
|
||||
end
|
||||
|
||||
@command.stage_seeds = stage_seeds
|
||||
@command.pipeline_seed = pipeline_seed
|
||||
end
|
||||
|
||||
def break?
|
||||
|
@ -42,24 +42,12 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def stage_seeds_errors
|
||||
stage_seeds.flat_map(&:errors).compact.presence
|
||||
end
|
||||
|
||||
def stage_seeds
|
||||
strong_memoize(:stage_seeds) do
|
||||
seeds = stages_attributes.inject([]) do |previous_stages, attributes|
|
||||
seed = Gitlab::Ci::Pipeline::Seed::Stage.new(pipeline, attributes, previous_stages)
|
||||
previous_stages + [seed]
|
||||
end
|
||||
|
||||
seeds.select(&:included?)
|
||||
def pipeline_seed
|
||||
strong_memoize(:pipeline_seed) do
|
||||
stages_attributes = @command.yaml_processor_result.stages_attributes
|
||||
Gitlab::Ci::Pipeline::Seed::Pipeline.new(pipeline, stages_attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def stages_attributes
|
||||
@command.yaml_processor_result.stages_attributes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,11 +34,7 @@ module Gitlab
|
|||
|
||||
def pipeline_deployment_count
|
||||
strong_memoize(:pipeline_deployment_count) do
|
||||
@command.stage_seeds.sum do |stage_seed|
|
||||
stage_seed.seeds.count do |build_seed|
|
||||
build_seed.attributes[:environment].present?
|
||||
end
|
||||
end
|
||||
@command.pipeline_seed.deployments_count
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Pipeline
|
||||
module Seed
|
||||
class Pipeline
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def initialize(pipeline, stages_attributes)
|
||||
@pipeline = pipeline
|
||||
@stages_attributes = stages_attributes
|
||||
end
|
||||
|
||||
def errors
|
||||
stage_seeds.flat_map(&:errors).compact.presence
|
||||
end
|
||||
|
||||
def stages
|
||||
stage_seeds.map(&:to_resource)
|
||||
end
|
||||
|
||||
def size
|
||||
stage_seeds.sum(&:size)
|
||||
end
|
||||
|
||||
def deployments_count
|
||||
stage_seeds.sum do |stage_seed|
|
||||
stage_seed.seeds.count do |build_seed|
|
||||
build_seed.attributes[:environment].present?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stage_seeds
|
||||
strong_memoize(:stage_seeds) do
|
||||
seeds = @stages_attributes.inject([]) do |previous_stages, attributes|
|
||||
seed = Gitlab::Ci::Pipeline::Seed::Stage.new(@pipeline, attributes, previous_stages)
|
||||
previous_stages + [seed]
|
||||
end
|
||||
|
||||
seeds.select(&:included?)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -689,16 +689,12 @@ module Gitlab
|
|||
end
|
||||
|
||||
def aggregated_metrics_monthly
|
||||
return {} unless Feature.enabled?(:product_analytics_aggregated_metrics)
|
||||
|
||||
{
|
||||
aggregated_metrics: ::Gitlab::UsageDataCounters::HLLRedisCounter.aggregated_metrics_monthly_data
|
||||
}
|
||||
end
|
||||
|
||||
def aggregated_metrics_weekly
|
||||
return {} unless Feature.enabled?(:product_analytics_aggregated_metrics)
|
||||
|
||||
{
|
||||
aggregated_metrics: ::Gitlab::UsageDataCounters::HLLRedisCounter.aggregated_metrics_weekly_data
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
- name: product_analytics_test_metrics_union
|
||||
operator: OR
|
||||
events: ['i_search_total', 'i_search_advanced', 'i_search_paid']
|
||||
feature_flag: product_analytics_aggregated_metrics
|
||||
- name: product_analytics_test_metrics_intersection
|
||||
operator: AND
|
||||
events: ['i_search_total', 'i_search_advanced', 'i_search_paid']
|
||||
|
|
|
@ -24634,6 +24634,12 @@ msgstr ""
|
|||
msgid "SecurityReports|Undo dismiss"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Upgrade to interact, track and shift left with vulnerability management features in the UI."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Upgrade to manage vulnerabilities"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|Vulnerability Report"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RuboCop
|
||||
module Cop
|
||||
module Gitlab
|
||||
# This cop checks for usage of boolean operators in rule blocks, which
|
||||
# does not work because conditions are objects, not booleans.
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad, `conducts_electricity` returns a Rule object, not a boolean!
|
||||
# rule { conducts_electricity && batteries }.enable :light_bulb
|
||||
#
|
||||
# # good
|
||||
# rule { conducts_electricity & batteries }.enable :light_bulb
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# # bad, `conducts_electricity` returns a Rule object, so the ternary is always going to be true
|
||||
# rule { conducts_electricity ? can?(:magnetize) : batteries }.enable :motor
|
||||
#
|
||||
# # good
|
||||
# rule { conducts_electricity & can?(:magnetize) }.enable :motor
|
||||
# rule { ~conducts_electricity & batteries }.enable :motor
|
||||
class PolicyRuleBoolean < RuboCop::Cop::Cop
|
||||
def_node_search :has_and_operator?, <<~PATTERN
|
||||
(and ...)
|
||||
PATTERN
|
||||
|
||||
def_node_search :has_or_operator?, <<~PATTERN
|
||||
(or ...)
|
||||
PATTERN
|
||||
|
||||
def_node_search :has_if?, <<~PATTERN
|
||||
(if ...)
|
||||
PATTERN
|
||||
|
||||
def on_block(node)
|
||||
return unless node.method_name == :rule
|
||||
|
||||
if has_and_operator?(node)
|
||||
add_offense(node, message: '&& is not allowed within a rule block. Did you mean to use `&`?')
|
||||
end
|
||||
|
||||
if has_or_operator?(node)
|
||||
add_offense(node, message: '|| is not allowed within a rule block. Did you mean to use `|`?')
|
||||
end
|
||||
|
||||
if has_if?(node)
|
||||
add_offense(node, message: 'if and ternary operators are not allowed within a rule block.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -153,6 +153,14 @@ RSpec.describe 'Filter issues', :js do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'filter by reviewer' do
|
||||
it 'does not allow filtering by reviewer' do
|
||||
find('.filtered-search').click
|
||||
|
||||
expect(page).not_to have_button('Reviewer')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'filter issues by label' do
|
||||
context 'only label' do
|
||||
it 'filters issues by searched label' do
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import Component from '~/groups/components/visibility_level_dropdown.vue';
|
||||
|
||||
describe('Visibility Level Dropdown', () => {
|
||||
let wrapper;
|
||||
|
||||
const options = [
|
||||
{ level: 0, label: 'Private', description: 'Private description' },
|
||||
{ level: 20, label: 'Public', description: 'Public description' },
|
||||
];
|
||||
const defaultLevel = 0;
|
||||
|
||||
const createComponent = propsData => {
|
||||
wrapper = shallowMount(Component, {
|
||||
propsData,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
visibilityLevelOptions: options,
|
||||
defaultLevel,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const hiddenInputValue = () =>
|
||||
wrapper.find("input[name='group[visibility_level]']").attributes('value');
|
||||
const dropdownText = () => wrapper.find(GlDropdown).props('text');
|
||||
const findDropdownItems = () =>
|
||||
wrapper.findAll(GlDropdownItem).wrappers.map(option => ({
|
||||
text: option.text(),
|
||||
secondaryText: option.props('secondaryText'),
|
||||
}));
|
||||
|
||||
describe('Default values', () => {
|
||||
it('sets the value of the hidden input to the default value', () => {
|
||||
expect(hiddenInputValue()).toBe(options[0].level.toString());
|
||||
});
|
||||
|
||||
it('sets the text of the dropdown to the default value', () => {
|
||||
expect(dropdownText()).toBe(options[0].label);
|
||||
});
|
||||
|
||||
it('shows all dropdown options', () => {
|
||||
expect(findDropdownItems()).toEqual(
|
||||
options.map(({ label, description }) => ({ text: label, secondaryText: description })),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Selecting an option', () => {
|
||||
beforeEach(() => {
|
||||
wrapper
|
||||
.findAll(GlDropdownItem)
|
||||
.at(1)
|
||||
.vm.$emit('click');
|
||||
});
|
||||
|
||||
it('sets the value of the hidden input to the selected value', () => {
|
||||
expect(hiddenInputValue()).toBe(options[1].level.toString());
|
||||
});
|
||||
|
||||
it('sets the text of the dropdown to the selected value', () => {
|
||||
expect(dropdownText()).toBe(options[1].label);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -12,8 +12,8 @@ import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.
|
|||
import TagsList from '~/registry/explorer/components/details_page/tags_list.vue';
|
||||
import EmptyTagsState from '~/registry/explorer/components/details_page/empty_tags_state.vue';
|
||||
|
||||
import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.graphql';
|
||||
import deleteContainerRepositoryTagsMutation from '~/registry/explorer/graphql/mutations/delete_container_repository_tags.graphql';
|
||||
import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.query.graphql';
|
||||
import deleteContainerRepositoryTagsMutation from '~/registry/explorer/graphql/mutations/delete_container_repository_tags.mutation.graphql';
|
||||
|
||||
import {
|
||||
graphQLImageDetailsMock,
|
||||
|
|
|
@ -19,9 +19,9 @@ import {
|
|||
SEARCH_PLACEHOLDER_TEXT,
|
||||
} from '~/registry/explorer/constants';
|
||||
|
||||
import getProjectContainerRepositories from '~/registry/explorer/graphql/queries/get_project_container_repositories.graphql';
|
||||
import getGroupContainerRepositories from '~/registry/explorer/graphql/queries/get_group_container_repositories.graphql';
|
||||
import deleteContainerRepository from '~/registry/explorer/graphql/mutations/delete_container_repository.graphql';
|
||||
import getProjectContainerRepositoriesQuery from '~/registry/explorer/graphql/queries/get_project_container_repositories.query.graphql';
|
||||
import getGroupContainerRepositoriesQuery from '~/registry/explorer/graphql/queries/get_group_container_repositories.query.graphql';
|
||||
import deleteContainerRepositoryMutation from '~/registry/explorer/graphql/mutations/delete_container_repository.mutation.graphql';
|
||||
|
||||
import {
|
||||
graphQLImageListMock,
|
||||
|
@ -72,9 +72,9 @@ describe('List Page', () => {
|
|||
localVue.use(VueApollo);
|
||||
|
||||
const requestHandlers = [
|
||||
[getProjectContainerRepositories, resolver],
|
||||
[getGroupContainerRepositories, groupResolver],
|
||||
[deleteContainerRepository, mutationResolver],
|
||||
[getProjectContainerRepositoriesQuery, resolver],
|
||||
[getGroupContainerRepositoriesQuery, groupResolver],
|
||||
[deleteContainerRepositoryMutation, mutationResolver],
|
||||
];
|
||||
|
||||
apolloProvider = createMockApollo(requestHandlers);
|
||||
|
|
|
@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo';
|
|||
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import createMockApollo from 'jest/helpers/mock_apollo_helper';
|
||||
import component from '~/registry/settings/components/registry_settings_app.vue';
|
||||
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql';
|
||||
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
|
||||
import SettingsForm from '~/registry/settings/components/settings_form.vue';
|
||||
import {
|
||||
FETCH_SETTINGS_ERROR_MESSAGE,
|
||||
|
|
|
@ -4,8 +4,8 @@ import createMockApollo from 'jest/helpers/mock_apollo_helper';
|
|||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import Tracking from '~/tracking';
|
||||
import component from '~/registry/settings/components/settings_form.vue';
|
||||
import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.graphql';
|
||||
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql';
|
||||
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';
|
||||
import {
|
||||
UPDATE_SETTINGS_ERROR_MESSAGE,
|
||||
UPDATE_SETTINGS_SUCCESS_MESSAGE,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update';
|
||||
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql';
|
||||
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.query.graphql';
|
||||
|
||||
describe('Registry settings cache update', () => {
|
||||
let client;
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import { GlLink, GlPopover } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue';
|
||||
|
||||
const helpPath = '/docs';
|
||||
const discoverProjectSecurityPath = '/discoverProjectSecurityPath';
|
||||
|
||||
describe('HelpIcon component', () => {
|
||||
let wrapper;
|
||||
|
||||
const createWrapper = props => {
|
||||
wrapper = shallowMount(HelpIcon, {
|
||||
propsData: {
|
||||
helpPath,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findLink = () => wrapper.find(GlLink);
|
||||
const findPopover = () => wrapper.find(GlPopover);
|
||||
const findPopoverTarget = () => wrapper.find({ ref: 'discoverProjectSecurity' });
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
describe('given a help path only', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
});
|
||||
|
||||
it('does not render a popover', () => {
|
||||
expect(findPopover().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders a help link', () => {
|
||||
expect(findLink().attributes()).toMatchObject({
|
||||
href: helpPath,
|
||||
target: '_blank',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a help path and discover project security path', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper({ discoverProjectSecurityPath });
|
||||
});
|
||||
|
||||
it('renders a popover', () => {
|
||||
const popover = findPopover();
|
||||
expect(popover.props('target')()).toBe(findPopoverTarget().element);
|
||||
expect(popover.attributes()).toMatchObject({
|
||||
title: HelpIcon.i18n.upgradeToManageVulnerabilities,
|
||||
triggers: 'click blur',
|
||||
});
|
||||
expect(popover.text()).toContain(HelpIcon.i18n.upgradeToInteract);
|
||||
});
|
||||
|
||||
it('renders a link to the discover path', () => {
|
||||
expect(findLink().attributes()).toMatchObject({
|
||||
href: discoverProjectSecurityPath,
|
||||
target: '_blank',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -19,6 +19,7 @@ import {
|
|||
REPORT_TYPE_SAST,
|
||||
REPORT_TYPE_SECRET_DETECTION,
|
||||
} from '~/vue_shared/security_reports/constants';
|
||||
import HelpIcon from '~/vue_shared/security_reports/components/help_icon.vue';
|
||||
import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue';
|
||||
import SecurityReportsApp from '~/vue_shared/security_reports/security_reports_app.vue';
|
||||
import securityReportDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_download_paths.query.graphql';
|
||||
|
@ -38,6 +39,7 @@ describe('Security reports app', () => {
|
|||
pipelineId: 123,
|
||||
projectId: 456,
|
||||
securityReportsDocsPath: '/docs',
|
||||
discoverProjectSecurityPath: '/discoverProjectSecurityPath',
|
||||
};
|
||||
|
||||
const createComponent = options => {
|
||||
|
@ -47,6 +49,9 @@ describe('Security reports app', () => {
|
|||
{
|
||||
localVue,
|
||||
propsData: { ...props },
|
||||
stubs: {
|
||||
HelpIcon: true,
|
||||
},
|
||||
},
|
||||
options,
|
||||
),
|
||||
|
@ -68,7 +73,7 @@ describe('Security reports app', () => {
|
|||
|
||||
const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown);
|
||||
const findPipelinesTabAnchor = () => wrapper.find('[data-testid="show-pipelines"]');
|
||||
const findHelpLink = () => wrapper.find('[data-testid="help"]');
|
||||
const findHelpIconComponent = () => wrapper.find(HelpIcon);
|
||||
const setupMockJobArtifact = reportType => {
|
||||
jest
|
||||
.spyOn(Api, 'pipelineJobs')
|
||||
|
@ -133,8 +138,9 @@ describe('Security reports app', () => {
|
|||
});
|
||||
|
||||
it('renders a help link', () => {
|
||||
expect(findHelpLink().attributes()).toMatchObject({
|
||||
href: props.securityReportsDocsPath,
|
||||
expect(findHelpIconComponent().props()).toEqual({
|
||||
helpPath: props.securityReportsDocsPath,
|
||||
discoverProjectSecurityPath: props.discoverProjectSecurityPath,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -284,4 +284,34 @@ RSpec.describe VisibilityLevelHelper do
|
|||
it { is_expected.to eq(expected) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#visibility_level_options' do
|
||||
let(:user) { build(:user) }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(user)
|
||||
end
|
||||
|
||||
it 'returns the desired mapping' do
|
||||
expected_options = [
|
||||
{
|
||||
level: 0,
|
||||
label: 'Private',
|
||||
description: 'The group and its projects can only be viewed by members.'
|
||||
},
|
||||
{
|
||||
level: 10,
|
||||
label: 'Internal',
|
||||
description: 'The group and any internal projects can be viewed by any logged in user except external users.'
|
||||
},
|
||||
{
|
||||
level: 20,
|
||||
label: 'Public',
|
||||
description: 'The group and any public projects can be viewed without any authentication.'
|
||||
}
|
||||
]
|
||||
|
||||
expect(helper.visibility_level_options(group)).to eq expected_options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,6 +64,11 @@ RSpec.describe Feature::Definition do
|
|||
expect { definition.valid_usage!(type_in_code: :development, default_enabled_in_code: false) }
|
||||
.to raise_error(/The `default_enabled:` of `feature_flag` is not equal to config/)
|
||||
end
|
||||
|
||||
it 'allows passing `default_enabled: :yaml`' do
|
||||
expect { definition.valid_usage!(type_in_code: :development, default_enabled_in_code: :yaml) }
|
||||
.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -209,4 +214,58 @@ RSpec.describe Feature::Definition do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.defaul_enabled?' do
|
||||
subject { described_class.default_enabled?(key) }
|
||||
|
||||
context 'when feature flag exist' do
|
||||
let(:key) { definition.key }
|
||||
|
||||
before do
|
||||
allow(described_class).to receive(:definitions) do
|
||||
{ definition.key => definition }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when default_enabled is true' do
|
||||
it 'returns the value from the definition' do
|
||||
expect(subject).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when default_enabled is false' do
|
||||
let(:attributes) do
|
||||
{ name: 'feature_flag',
|
||||
type: 'development',
|
||||
default_enabled: false }
|
||||
end
|
||||
|
||||
it 'returns the value from the definition' do
|
||||
expect(subject).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when feature flag does not exist' do
|
||||
let(:key) { :unknown_feature_flag }
|
||||
|
||||
context 'when on dev or test environment' do
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(
|
||||
Feature::InvalidFeatureFlagError,
|
||||
"The feature flag YAML definition for 'unknown_feature_flag' does not exist")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when on production environment' do
|
||||
before do
|
||||
allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -249,10 +249,12 @@ RSpec.describe Feature, stub_feature_flags: false do
|
|||
Feature::Definition.new('development/my_feature_flag.yml',
|
||||
name: 'my_feature_flag',
|
||||
type: 'development',
|
||||
default_enabled: false
|
||||
default_enabled: default_enabled
|
||||
).tap(&:validate!)
|
||||
end
|
||||
|
||||
let(:default_enabled) { false }
|
||||
|
||||
before do
|
||||
stub_env('LAZILY_CREATE_FEATURE_FLAG', '0')
|
||||
|
||||
|
@ -275,6 +277,63 @@ RSpec.describe Feature, stub_feature_flags: false do
|
|||
expect { described_class.enabled?(:my_feature_flag, default_enabled: true) }
|
||||
.to raise_error(/The `default_enabled:` of/)
|
||||
end
|
||||
|
||||
context 'when `default_enabled: :yaml` is used in code' do
|
||||
it 'reads the default from the YAML definition' do
|
||||
expect(described_class.enabled?(:my_feature_flag, default_enabled: :yaml)).to eq(false)
|
||||
end
|
||||
|
||||
context 'when default_enabled is true in the YAML definition' do
|
||||
let(:default_enabled) { true }
|
||||
|
||||
it 'reads the default from the YAML definition' do
|
||||
expect(described_class.enabled?(:my_feature_flag, default_enabled: :yaml)).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when YAML definition does not exist for an optional type' do
|
||||
let(:optional_type) { described_class::Shared::TYPES.find { |name, attrs| attrs[:optional] }.first }
|
||||
|
||||
context 'when in dev or test environment' do
|
||||
it 'raises an error for dev' do
|
||||
expect { described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml) }
|
||||
.to raise_error(
|
||||
Feature::InvalidFeatureFlagError,
|
||||
"The feature flag YAML definition for 'non_existent_flag' does not exist")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when in production' do
|
||||
before do
|
||||
allow(Gitlab::ErrorTracking).to receive(:should_raise_for_dev?).and_return(false)
|
||||
end
|
||||
|
||||
context 'when database exists' do
|
||||
before do
|
||||
allow(Gitlab::Database).to receive(:exists?).and_return(true)
|
||||
end
|
||||
|
||||
it 'checks the persisted status and returns false' do
|
||||
expect(described_class).to receive(:get).with(:non_existent_flag).and_call_original
|
||||
|
||||
expect(described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml)).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when database does not exist' do
|
||||
before do
|
||||
allow(Gitlab::Database).to receive(:exists?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns false without checking the status in the database' do
|
||||
expect(described_class).not_to receive(:get)
|
||||
|
||||
expect(described_class.enabled?(:non_existent_flag, type: optional_type, default_enabled: :yaml)).to eq(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,26 +7,13 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
|
|||
let_it_be(:project, reload: true) { create(:project, namespace: namespace) }
|
||||
let_it_be(:plan_limits, reload: true) { create(:plan_limits, :default_plan) }
|
||||
|
||||
let(:stage_seeds) do
|
||||
[
|
||||
double(:test, seeds: [
|
||||
double(:test, attributes: {})
|
||||
]),
|
||||
double(:staging, seeds: [
|
||||
double(:staging, attributes: { environment: 'staging' })
|
||||
]),
|
||||
double(:production, seeds: [
|
||||
double(:production, attributes: { environment: 'production' })
|
||||
])
|
||||
]
|
||||
end
|
||||
|
||||
let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2) }
|
||||
let(:save_incompleted) { false }
|
||||
|
||||
let(:command) do
|
||||
double(:command,
|
||||
project: project,
|
||||
stage_seeds: stage_seeds,
|
||||
pipeline_seed: pipeline_seed,
|
||||
save_incompleted: save_incompleted
|
||||
)
|
||||
end
|
||||
|
|
|
@ -50,8 +50,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
|
|||
it 'sets the seeds in the command object' do
|
||||
run_chain
|
||||
|
||||
expect(command.stage_seeds).to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
|
||||
expect(command.stage_seeds.count).to eq 1
|
||||
expect(command.pipeline_seed).to be_a(Gitlab::Ci::Pipeline::Seed::Pipeline)
|
||||
expect(command.pipeline_seed.size).to eq 1
|
||||
end
|
||||
|
||||
context 'when no ref policy is specified' do
|
||||
|
@ -63,16 +63,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
|
|||
}
|
||||
end
|
||||
|
||||
it 'correctly fabricates a stage seeds object' do
|
||||
it 'correctly fabricates stages and builds' do
|
||||
run_chain
|
||||
|
||||
seeds = command.stage_seeds
|
||||
expect(seeds.size).to eq 2
|
||||
expect(seeds.first.attributes[:name]).to eq 'test'
|
||||
expect(seeds.second.attributes[:name]).to eq 'deploy'
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'rspec'
|
||||
expect(seeds.dig(0, 1, :name)).to eq 'spinach'
|
||||
expect(seeds.dig(1, 0, :name)).to eq 'production'
|
||||
seed = command.pipeline_seed
|
||||
|
||||
expect(seed.stages.size).to eq 2
|
||||
expect(seed.size).to eq 3
|
||||
expect(seed.stages.first.name).to eq 'test'
|
||||
expect(seed.stages.second.name).to eq 'deploy'
|
||||
expect(seed.stages[0].statuses[0].name).to eq 'rspec'
|
||||
expect(seed.stages[0].statuses[1].name).to eq 'spinach'
|
||||
expect(seed.stages[1].statuses[0].name).to eq 'production'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -88,14 +90,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
|
|||
}
|
||||
end
|
||||
|
||||
it 'returns stage seeds only assigned to master' do
|
||||
it 'returns pipeline seed with jobs only assigned to master' do
|
||||
run_chain
|
||||
|
||||
seeds = command.stage_seeds
|
||||
seed = command.pipeline_seed
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.first.attributes[:name]).to eq 'test'
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
expect(seed.size).to eq 1
|
||||
expect(seed.stages.first.name).to eq 'test'
|
||||
expect(seed.stages[0].statuses[0].name).to eq 'spinach'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -109,14 +111,14 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
|
|||
}
|
||||
end
|
||||
|
||||
it 'returns stage seeds only assigned to schedules' do
|
||||
it 'returns pipeline seed with jobs only assigned to schedules' do
|
||||
run_chain
|
||||
|
||||
seeds = command.stage_seeds
|
||||
seed = command.pipeline_seed
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.first.attributes[:name]).to eq 'test'
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
expect(seed.size).to eq 1
|
||||
expect(seed.stages.first.name).to eq 'test'
|
||||
expect(seed.stages[0].statuses[0].name).to eq 'spinach'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -141,11 +143,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
|
|||
it 'returns seeds for kubernetes dependent job' do
|
||||
run_chain
|
||||
|
||||
seeds = command.stage_seeds
|
||||
seed = command.pipeline_seed
|
||||
|
||||
expect(seeds.size).to eq 2
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
expect(seeds.dig(1, 0, :name)).to eq 'production'
|
||||
expect(seed.size).to eq 2
|
||||
expect(seed.stages[0].statuses[0].name).to eq 'spinach'
|
||||
expect(seed.stages[1].statuses[0].name).to eq 'production'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -154,10 +156,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
|
|||
it 'does not return seeds for kubernetes dependent job' do
|
||||
run_chain
|
||||
|
||||
seeds = command.stage_seeds
|
||||
seed = command.pipeline_seed
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
expect(seed.size).to eq 1
|
||||
expect(seed.stages[0].statuses[0].name).to eq 'spinach'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -173,10 +175,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do
|
|||
it 'returns stage seeds only when variables expression is truthy' do
|
||||
run_chain
|
||||
|
||||
seeds = command.stage_seeds
|
||||
seed = command.pipeline_seed
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'unit'
|
||||
expect(seed.size).to eq 1
|
||||
expect(seed.stages[0].statuses[0].name).to eq 'unit'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -10,24 +10,12 @@ RSpec.describe Gitlab::Ci::Pipeline::Quota::Deployments do
|
|||
|
||||
let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
|
||||
|
||||
let(:stage_seeds) do
|
||||
[
|
||||
double(:test, seeds: [
|
||||
double(:test, attributes: {})
|
||||
]),
|
||||
double(:staging, seeds: [
|
||||
double(:staging, attributes: { environment: 'staging' })
|
||||
]),
|
||||
double(:production, seeds: [
|
||||
double(:production, attributes: { environment: 'production' })
|
||||
])
|
||||
]
|
||||
end
|
||||
let(:pipeline_seed) { double(:pipeline_seed, deployments_count: 2)}
|
||||
|
||||
let(:command) do
|
||||
double(:command,
|
||||
project: project,
|
||||
stage_seeds: stage_seeds,
|
||||
pipeline_seed: pipeline_seed,
|
||||
save_incompleted: true
|
||||
)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Pipeline::Seed::Pipeline do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
|
||||
let(:stages_attributes) do
|
||||
[
|
||||
{
|
||||
name: 'build',
|
||||
index: 0,
|
||||
builds: [
|
||||
{ name: 'init', scheduling_type: :stage },
|
||||
{ name: 'build', scheduling_type: :stage }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
index: 1,
|
||||
builds: [
|
||||
{ name: 'rspec', scheduling_type: :stage },
|
||||
{ name: 'staging', scheduling_type: :stage, environment: 'staging' },
|
||||
{ name: 'deploy', scheduling_type: :stage, environment: 'production' }
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
subject(:seed) do
|
||||
described_class.new(pipeline, stages_attributes)
|
||||
end
|
||||
|
||||
describe '#stages' do
|
||||
it 'returns the stage resources' do
|
||||
stages = seed.stages
|
||||
|
||||
expect(stages).to all(be_a(Ci::Stage))
|
||||
expect(stages.map(&:name)).to contain_exactly('build', 'test')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#size' do
|
||||
it 'returns the number of jobs' do
|
||||
expect(seed.size).to eq(5)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#errors' do
|
||||
context 'when attributes are valid' do
|
||||
it 'returns nil' do
|
||||
expect(seed.errors).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when attributes are not valid' do
|
||||
it 'returns the errors' do
|
||||
stages_attributes[0][:builds] << {
|
||||
name: 'invalid_job',
|
||||
scheduling_type: :dag,
|
||||
needs_attributes: [{ name: 'non-existent', artifacts: true }]
|
||||
}
|
||||
|
||||
expect(seed.errors).to contain_exactly("invalid_job: needs 'non-existent'")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deployments_count' do
|
||||
it 'counts the jobs having an environment associated' do
|
||||
expect(seed.deployments_count).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1255,45 +1255,21 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'aggregated_metrics' do
|
||||
shared_examples 'aggregated_metrics_for_time_range' do
|
||||
context 'with product_analytics_aggregated_metrics feature flag on' do
|
||||
before do
|
||||
stub_feature_flags(product_analytics_aggregated_metrics: true)
|
||||
end
|
||||
describe '.aggregated_metrics_weekly' do
|
||||
subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_weekly }
|
||||
|
||||
it 'uses ::Gitlab::UsageDataCounters::HLLRedisCounter#aggregated_metrics_data', :aggregate_failures do
|
||||
expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(aggregated_metrics_data_method).and_return(global_search_gmau: 123)
|
||||
expect(aggregated_metrics_payload).to eq(aggregated_metrics: { global_search_gmau: 123 })
|
||||
end
|
||||
end
|
||||
|
||||
context 'with product_analytics_aggregated_metrics feature flag off' do
|
||||
before do
|
||||
stub_feature_flags(product_analytics_aggregated_metrics: false)
|
||||
end
|
||||
|
||||
it 'returns empty hash', :aggregate_failures do
|
||||
expect(::Gitlab::UsageDataCounters::HLLRedisCounter).not_to receive(aggregated_metrics_data_method)
|
||||
expect(aggregated_metrics_payload).to be {}
|
||||
end
|
||||
end
|
||||
it 'uses ::Gitlab::UsageDataCounters::HLLRedisCounter#aggregated_metrics_data', :aggregate_failures do
|
||||
expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:aggregated_metrics_weekly_data).and_return(global_search_gmau: 123)
|
||||
expect(aggregated_metrics_payload).to eq(aggregated_metrics: { global_search_gmau: 123 })
|
||||
end
|
||||
end
|
||||
|
||||
describe '.aggregated_metrics_weekly' do
|
||||
subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_weekly }
|
||||
describe '.aggregated_metrics_monthly' do
|
||||
subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_monthly }
|
||||
|
||||
let(:aggregated_metrics_data_method) { :aggregated_metrics_weekly_data }
|
||||
|
||||
it_behaves_like 'aggregated_metrics_for_time_range'
|
||||
end
|
||||
|
||||
describe '.aggregated_metrics_monthly' do
|
||||
subject(:aggregated_metrics_payload) { described_class.aggregated_metrics_monthly }
|
||||
|
||||
let(:aggregated_metrics_data_method) { :aggregated_metrics_monthly_data }
|
||||
|
||||
it_behaves_like 'aggregated_metrics_for_time_range'
|
||||
it 'uses ::Gitlab::UsageDataCounters::HLLRedisCounter#aggregated_metrics_data', :aggregate_failures do
|
||||
expect(::Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:aggregated_metrics_monthly_data).and_return(global_search_gmau: 123)
|
||||
expect(aggregated_metrics_payload).to eq(aggregated_metrics: { global_search_gmau: 123 })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'rubocop'
|
||||
require 'rubocop/rspec/support'
|
||||
require_relative '../../../../rubocop/cop/gitlab/policy_rule_boolean'
|
||||
|
||||
RSpec.describe RuboCop::Cop::Gitlab::PolicyRuleBoolean, type: :rubocop do
|
||||
include CopHelper
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
it 'registers offense for &&' do
|
||||
expect_offense(<<~SOURCE)
|
||||
rule { conducts_electricity && batteries }.enable :light_bulb
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ && is not allowed within a rule block. Did you mean to use `&`?
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'registers offense for ||' do
|
||||
expect_offense(<<~SOURCE)
|
||||
rule { conducts_electricity || batteries }.enable :light_bulb
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ || is not allowed within a rule block. Did you mean to use `|`?
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'registers offense for if' do
|
||||
expect_offense(<<~SOURCE)
|
||||
rule { if conducts_electricity then can?(:magnetize) else batteries end }.enable :motor
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if and ternary operators are not allowed within a rule block.
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'registers offense for ternary operator' do
|
||||
expect_offense(<<~SOURCE)
|
||||
rule { conducts_electricity ? can?(:magnetize) : batteries }.enable :motor
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ if and ternary operators are not allowed within a rule block.
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'registers no offense for &' do
|
||||
expect_no_offenses(<<~SOURCE)
|
||||
rule { conducts_electricity & batteries }.enable :light_bulb
|
||||
SOURCE
|
||||
end
|
||||
|
||||
it 'registers no offense for |' do
|
||||
expect_no_offenses(<<~SOURCE)
|
||||
rule { conducts_electricity | batteries }.enable :light_bulb
|
||||
SOURCE
|
||||
end
|
||||
end
|
|
@ -5,19 +5,20 @@ require 'spec_helper'
|
|||
RSpec.describe StubFeatureFlags do
|
||||
let_it_be(:dummy_feature_flag) { :dummy_feature_flag }
|
||||
|
||||
let_it_be(:dummy_definition) do
|
||||
Feature::Definition.new(
|
||||
nil,
|
||||
name: dummy_feature_flag,
|
||||
type: 'development',
|
||||
default_enabled: false
|
||||
)
|
||||
end
|
||||
|
||||
# We inject dummy feature flag defintion
|
||||
# to ensure that we strong validate it's usage
|
||||
# as well
|
||||
before(:all) do
|
||||
definition = Feature::Definition.new(
|
||||
nil,
|
||||
name: dummy_feature_flag,
|
||||
type: 'development',
|
||||
# we allow ambigious usage of `default_enabled:`
|
||||
default_enabled: [false, true]
|
||||
)
|
||||
|
||||
Feature::Definition.definitions[dummy_feature_flag] = definition
|
||||
Feature::Definition.definitions[dummy_feature_flag] = dummy_definition
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
|
@ -47,6 +48,10 @@ RSpec.describe StubFeatureFlags do
|
|||
it { expect(Feature.disabled?(feature_name)).not_to eq(expected_result) }
|
||||
|
||||
context 'default_enabled does not impact feature state' do
|
||||
before do
|
||||
allow(dummy_definition).to receive(:default_enabled).and_return(true)
|
||||
end
|
||||
|
||||
it { expect(Feature.enabled?(feature_name, default_enabled: true)).to eq(expected_result) }
|
||||
it { expect(Feature.disabled?(feature_name, default_enabled: true)).not_to eq(expected_result) }
|
||||
end
|
||||
|
@ -79,6 +84,10 @@ RSpec.describe StubFeatureFlags do
|
|||
it { expect(Feature.disabled?(feature_name, actor(tested_actor))).not_to eq(expected_result) }
|
||||
|
||||
context 'default_enabled does not impact feature state' do
|
||||
before do
|
||||
allow(dummy_definition).to receive(:default_enabled).and_return(true)
|
||||
end
|
||||
|
||||
it { expect(Feature.enabled?(feature_name, actor(tested_actor), default_enabled: true)).to eq(expected_result) }
|
||||
it { expect(Feature.disabled?(feature_name, actor(tested_actor), default_enabled: true)).not_to eq(expected_result) }
|
||||
end
|
||||
|
|
|
@ -8,6 +8,13 @@ RSpec.describe GitlabUsagePingWorker, :clean_gitlab_redis_shared_state do
|
|||
allow(subject).to receive(:sleep)
|
||||
end
|
||||
|
||||
it 'does not run for GitLab.com' do
|
||||
allow(Gitlab).to receive(:com?).and_return(true)
|
||||
expect(SubmitUsagePingService).not_to receive(:new)
|
||||
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'delegates to SubmitUsagePingService' do
|
||||
expect_next_instance_of(SubmitUsagePingService) { |service| expect(service).to receive(:execute) }
|
||||
|
||||
|
|
Loading…
Reference in New Issue