Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-10-27 12:09:41 +00:00
parent ef96d56ed3
commit 0a559c913e
74 changed files with 505 additions and 227 deletions

View file

@ -9,5 +9,5 @@
<!-- Describe the audit event you are proposing should be added, including any details of what should be captured, how, and why. -->
/label ~"Category:Audit Events"
/label ~"feature"
/label ~"type::feature"
/label ~"group::compliance"

View file

@ -2,10 +2,10 @@
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
filtered by the "regression" or "bug" label:
filtered by the "regression" or "type::bug" label:
- https://gitlab.com/gitlab-org/gitlab/issues?label_name%5B%5D=regression
- https://gitlab.com/gitlab-org/gitlab/issues?label_name%5B%5D=bug
- https://gitlab.com/gitlab-org/gitlab/issues?label_name%5B%5D=type::bug
and verify the issue you're about to submit isn't a duplicate.
--->
@ -82,4 +82,4 @@ will also determine whether the bug is fixed in a more recent version. -->
<!-- If you can, link to the line of code that might be responsible for the problem. -->
/label ~bug
/label ~"type::bug"

View file

@ -17,4 +17,4 @@ The changes need to become an official part of the product.
- [ ] After the flag removal is deployed, [clean up the feature/experiment feature flags](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel
- [ ] Ensure the corresponding [Experiment Tracking](https://gitlab.com/groups/gitlab-org/-/boards/1352542?label_name[]=devops%3A%3Agrowth&label_name[]=growth%20experiment&label_name[]=experiment%20tracking) issue is updated
/label ~"feature" ~"feature::maintenance" ~"workflow::scheduling" ~"growth experiment" ~"feature flag"
/label ~"type::maintenance" ~"workflow::scheduling" ~"growth experiment" ~"feature flag"

View file

@ -7,4 +7,4 @@
<!-- Consider adding related issues and epics to this issue. You can also reference the Feature Proposal Template (https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/issue_templates/Feature%20proposal%20-%20detailed.md) for additional details to consider adding to this issue. Additionally, as a data oriented organization, when your feature exits planning breakdown, consider adding the `What does success look like, and how can we measure that?` section.
-->
/label ~feature::addition ~"group::" ~"section::" ~"Category:" ~"GitLab Core"/~"GitLab Premium"/~"GitLab Ultimate"
/label ~"type::feature" ~feature::addition ~"group::" ~"section::" ~"Category:" ~"GitLab Core"/~"GitLab Premium"/~"GitLab Ultimate"

View file

@ -14,7 +14,7 @@
/label ~"feature" ~"group::" ~"section::" ~"Category::" ~"GitLab Free"/~"GitLab Premium"/~"GitLab Ultimate"
/label ~"type::feature" ~"group::" ~"section::" ~"Category::" ~"GitLab Free"/~"GitLab Premium"/~"GitLab Ultimate"
<!--- Use the following resources to find the appropriate labels:

View file

@ -112,6 +112,4 @@ Use the following resources to find the appropriate labels:
-->
/label ~devops:: ~group: ~Category:
/label ~"GitLab Free"/~"GitLab Premium"/~"GitLab Ultimate"
/label ~feature
/label ~documentation
/label ~direction
/label ~"type::feature" ~documentation ~direction

View file

@ -53,4 +53,4 @@ See also:
-->
/label ~"infradev"
/label ~"bug"
/label ~"type::bug"

View file

@ -41,9 +41,9 @@ please list them here.
<!--
Please select the appropriate label from the following:
~"feature::addition"
~"feature::maintenance"
~"type::maintenance"
~"tooling::pipelines"
~"tooling::workflow"
-->
/label ~"feature::maintenance"
/label ~"type::maintenance"

View file

@ -29,5 +29,5 @@
### Team
- [ ] Add ~"workflow::planning breakdown" ~feature and the corresponding `~devops::<stage>` and `~group::<group>` labels.
- [ ] Add ~"workflow::planning breakdown" ~"type::feature" and the corresponding `~devops::<stage>` and `~group::<group>` labels.
- [ ] Ping the PM and EM.

View file

@ -20,10 +20,10 @@
If you are only adding documentation, do not add any of the following labels:
- `~"feature"`
- `~"type::feature"`
- `~"frontend"`
- `~"backend"`
- `~"bug"`
- `~"type::bug"`
- `~"database"`
These labels cause the MR to be added to code verification QA issues.

View file

@ -24,5 +24,5 @@ Please link to the respective test case in the testcases project
/label ~"Quality:test-gap" ~"Quality:EE test gaps"
<!-- Select the appropriate feature label, ~"feature::addition" for tests added for new features, ~"feature::maintenance" for tests added for existing features -->
/label ~"feature::addition" ~"feature::maintenance"
<!-- Select the appropriate feature label, ~"feature::addition" for tests added for new features, ~"type::maintenance" for tests added for existing features -->
/label ~"feature::addition" ~"type::maintenance"

View file

@ -35,4 +35,4 @@ This will help keep track of expected cost increases to the [GitLab project aver
- [ ] Consider communicating these changes to the broader team following the [communication guideline for pipeline changes](https://about.gitlab.com/handbook/engineering/quality/engineering-productivity/#pipeline-changes)
/label ~tooling ~"tooling::pipelines" ~"Engineering Productivity"
/label ~"type::tooling" ~"tooling::pipelines" ~"Engineering Productivity"

View file

@ -30,7 +30,7 @@ the noise (due to constantly failing tests, flaky tests, and so on) so that new
- [ ] To ensure a faster turnaround, ask in the `#quality` Slack channel for someone to review and merge the merge request, rather than assigning it directly.
<!-- Base labels. -->
/label ~"Quality" ~"QA" ~"feature" ~"feature::maintenance"
/label ~"Quality" ~"QA" ~"type::maintenance"
<!--
Choose the stage that appears in the test path, e.g. ~"devops::create" for

View file

@ -1 +1 @@
ba8bc12dd0ff83fc726c6c09ee0cb0ebc311a748
d99f9cbac9164647666f1778eb912568c261813d

View file

@ -36,6 +36,7 @@ import {
TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_MY_REACTION,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE,
TOKEN_TYPE_WEIGHT,
UPDATED_DESC,
@ -65,6 +66,7 @@ import {
TOKEN_TITLE_LABEL,
TOKEN_TITLE_MILESTONE,
TOKEN_TITLE_MY_REACTION,
TOKEN_TITLE_RELEASE,
TOKEN_TITLE_TYPE,
TOKEN_TITLE_WEIGHT,
} from '~/vue_shared/components/filtered_search_bar/constants';
@ -88,6 +90,8 @@ const LabelToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/label_token.vue');
const MilestoneToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue');
const ReleaseToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/release_token.vue');
const WeightToken = () =>
import('~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue');
@ -165,6 +169,9 @@ export default {
newIssuePath: {
default: '',
},
releasesPath: {
default: '',
},
rssPath: {
default: '',
},
@ -317,7 +324,6 @@ export default {
title: TOKEN_TITLE_MILESTONE,
icon: 'clock',
token: MilestoneToken,
unique: true,
fetchMilestones: this.fetchMilestones,
},
{
@ -341,6 +347,16 @@ export default {
},
];
if (this.isProject) {
tokens.push({
type: TOKEN_TYPE_RELEASE,
title: TOKEN_TITLE_RELEASE,
icon: 'rocket',
token: ReleaseToken,
fetchReleases: this.fetchReleases,
});
}
if (this.isSignedIn) {
tokens.push({
type: TOKEN_TYPE_MY_REACTION,
@ -371,7 +387,6 @@ export default {
title: TOKEN_TITLE_ITERATION,
icon: 'iteration',
token: IterationToken,
unique: true,
fetchIterations: this.fetchIterations,
});
}
@ -457,6 +472,9 @@ export default {
fetchEmojis(search) {
return this.fetchWithCache(this.autocompleteAwardEmojisPath, 'emojis', 'name', search);
},
fetchReleases(search) {
return this.fetchWithCache(this.releasesPath, 'releases', 'tag', search);
},
fetchLabels(search) {
return this.$apollo
.query({

View file

@ -166,6 +166,7 @@ const LABEL_PRIORITY_ASC_SORT = 'label_priority_asc';
const POPULARITY_ASC_SORT = 'popularity_asc';
const WEIGHT_DESC_SORT = 'weight_desc';
const BLOCKING_ISSUES_DESC_SORT = 'blocking_issues_desc';
const TITLE_ASC_SORT = 'title_asc';
const TITLE_DESC_SORT = 'title_desc';
export const urlSortParams = {
@ -187,7 +188,7 @@ export const urlSortParams = {
[WEIGHT_ASC]: WEIGHT,
[WEIGHT_DESC]: WEIGHT_DESC_SORT,
[BLOCKING_ISSUES_DESC]: BLOCKING_ISSUES_DESC_SORT,
[TITLE_ASC]: TITLE,
[TITLE_ASC]: TITLE_ASC_SORT,
[TITLE_DESC]: TITLE_DESC_SORT,
};
@ -211,6 +212,7 @@ export const TOKEN_TYPE_ASSIGNEE = 'assignee_username';
export const TOKEN_TYPE_MILESTONE = 'milestone';
export const TOKEN_TYPE_LABEL = 'labels';
export const TOKEN_TYPE_TYPE = 'type';
export const TOKEN_TYPE_RELEASE = 'release';
export const TOKEN_TYPE_MY_REACTION = 'my_reaction_emoji';
export const TOKEN_TYPE_CONFIDENTIAL = 'confidential';
export const TOKEN_TYPE_ITERATION = 'iteration';
@ -291,6 +293,21 @@ export const filters = {
},
},
},
[TOKEN_TYPE_RELEASE]: {
[API_PARAM]: {
[NORMAL_FILTER]: 'releaseTag',
[SPECIAL_FILTER]: 'releaseTagWildcardId',
},
[URL_PARAM]: {
[OPERATOR_IS]: {
[NORMAL_FILTER]: 'release_tag',
[SPECIAL_FILTER]: 'release_tag',
},
[OPERATOR_IS_NOT]: {
[NORMAL_FILTER]: 'not[release_tag]',
},
},
},
[TOKEN_TYPE_MY_REACTION]: {
[API_PARAM]: {
[NORMAL_FILTER]: 'myReactionEmoji',

View file

@ -137,6 +137,7 @@ export function mountIssuesListApp() {
newIssuePath,
projectImportJiraPath,
quickActionsHelpPath,
releasesPath,
resetPath,
rssPath,
showNewIssueLink,
@ -164,6 +165,7 @@ export function mountIssuesListApp() {
isSignedIn: parseBoolean(isSignedIn),
jiraIntegrationPath,
newIssuePath,
releasesPath,
rssPath,
showNewIssueLink: parseBoolean(showNewIssueLink),
signInPath,

View file

@ -16,6 +16,8 @@ query getIssues(
$milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!]
$not: NegatedIssueFilterInput
$beforeCursor: String
@ -66,6 +68,8 @@ query getIssues(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
not: $not
before: $beforeCursor

View file

@ -10,6 +10,8 @@ query getIssuesCount(
$milestoneTitle: [String]
$milestoneWildcardId: MilestoneWildcardId
$myReactionEmoji: String
$releaseTag: [String!]
$releaseTagWildcardId: ReleaseTagWildcardId
$types: [IssueType!]
$not: NegatedIssueFilterInput
) {
@ -78,6 +80,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
not: $not
) {
@ -94,6 +98,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
not: $not
) {
@ -110,6 +116,8 @@ query getIssuesCount(
milestoneTitle: $milestoneTitle
milestoneWildcardId: $milestoneWildcardId
myReactionEmoji: $myReactionEmoji
releaseTag: $releaseTag
releaseTagWildcardId: $releaseTagWildcardId
types: $types
not: $not
) {

View file

@ -21,10 +21,13 @@ import {
RELATIVE_POSITION_ASC,
SPECIAL_FILTER,
SPECIAL_FILTER_VALUES,
TITLE_ASC,
TITLE_DESC,
TOKEN_TYPE_ASSIGNEE,
TOKEN_TYPE_CONFIDENTIAL,
TOKEN_TYPE_ITERATION,
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE,
UPDATED_ASC,
UPDATED_DESC,
@ -114,11 +117,19 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
descending: RELATIVE_POSITION_ASC,
},
},
{
id: 9,
title: __('Title'),
sortDirection: {
ascending: TITLE_ASC,
descending: TITLE_DESC,
},
},
];
if (hasIssueWeightsFeature) {
sortOptions.push({
id: 9,
id: sortOptions.length + 1,
title: __('Weight'),
sortDirection: {
ascending: WEIGHT_ASC,
@ -129,7 +140,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
if (hasBlockedIssuesFeature) {
sortOptions.push({
id: 10,
id: sortOptions.length + 1,
title: __('Blocking'),
sortDirection: {
ascending: BLOCKING_ISSUES_DESC,
@ -194,9 +205,10 @@ const getFilterType = (data, tokenType = '') =>
? SPECIAL_FILTER
: NORMAL_FILTER;
const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_RELEASE];
const isWildcardValue = (tokenType, value) =>
(tokenType === TOKEN_TYPE_ITERATION || tokenType === TOKEN_TYPE_MILESTONE) &&
SPECIAL_FILTER_VALUES.includes(value);
wildcardTokens.includes(tokenType) && SPECIAL_FILTER_VALUES.includes(value);
const requiresUpperCaseValue = (tokenType, value) =>
tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value);

View file

@ -209,17 +209,25 @@ export default {
<status-icon v-if="data.icon" :icon-name="data.icon.name" :size="12" class="gl-pl-0" />
<gl-intersection-observer
:options="{ rootMargin: '100px', thresholds: 0.1 }"
class="gl-flex-wrap gl-align-self-center gl-display-flex"
class="gl-flex-wrap gl-display-flex gl-w-full"
@appear="appear(index)"
@disappear="disappear(index)"
>
<div v-safe-html="data.text" class="gl-mr-4"></div>
<div
v-safe-html="data.text"
class="gl-mr-4 gl-display-flex gl-align-items-center"
></div>
<div v-if="data.link">
<gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
</div>
<gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
{{ data.badge.text }}
</gl-badge>
<actions
:widget="$options.label || $options.name"
:tertiary-buttons="data.actions"
class="gl-ml-auto"
/>
</gl-intersection-observer>
</li>
</smart-virtual-list>

View file

@ -67,6 +67,7 @@ export default {
// href: 'https://google.com', // Required: href for the link
// text: 'Link text', // Required: Text to be used inside the link
// },
actions: [{ text: 'Full report', href: 'https://gitlab.com', target: '_blank' }],
}));
});
},

View file

@ -53,6 +53,7 @@ export const TOKEN_TITLE_ASSIGNEE = __('Assignee');
export const TOKEN_TITLE_MILESTONE = __('Milestone');
export const TOKEN_TITLE_LABEL = __('Label');
export const TOKEN_TITLE_TYPE = __('Type');
export const TOKEN_TITLE_RELEASE = __('Release');
export const TOKEN_TITLE_MY_REACTION = __('My-Reaction');
export const TOKEN_TITLE_CONFIDENTIAL = __('Confidential');
export const TOKEN_TITLE_ITERATION = __('Iteration');

View file

@ -0,0 +1,85 @@
<script>
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import createFlash from '~/flash';
import { __ } from '~/locale';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import { DEFAULT_NONE_ANY } from '../constants';
export default {
components: {
BaseToken,
GlFilteredSearchSuggestion,
},
props: {
active: {
type: Boolean,
required: true,
},
config: {
type: Object,
required: true,
},
value: {
type: Object,
required: true,
},
},
data() {
return {
releases: this.config.initialReleases || [],
loading: false,
};
},
computed: {
defaultReleases() {
return this.config.defaultReleases || DEFAULT_NONE_ANY;
},
},
methods: {
getActiveRelease(releases, data) {
return releases.find((release) => release.tag.toLowerCase() === data.toLowerCase());
},
fetchReleases(searchTerm) {
this.loading = true;
this.config
.fetchReleases(searchTerm)
.then((response) => {
this.releases = response;
})
.catch(() => {
createFlash({ message: __('There was a problem fetching releases.') });
})
.finally(() => {
this.loading = false;
});
},
},
};
</script>
<template>
<base-token
:active="active"
:config="config"
:value="value"
:default-suggestions="defaultReleases"
:suggestions="releases"
:suggestions-loading="loading"
:get-active-token-value="getActiveRelease"
@fetch-suggestions="fetchReleases"
v-on="$listeners"
>
<template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
{{ activeTokenValue ? activeTokenValue.tag : inputValue }}
</template>
<template #suggestions-list="{ suggestions }">
<gl-filtered-search-suggestion
v-for="release in suggestions"
:key="release.id"
:value="release.tag"
>
{{ release.tag }}
</gl-filtered-search-suggestion>
</template>
</base-token>
</template>

View file

@ -48,9 +48,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
before_action only: :show do
real_time_enabled = Gitlab::ActionCable::Config.in_app? || Feature.enabled?(:real_time_issue_sidebar, @project)
push_to_gon_attributes(:features, :real_time_issue_sidebar, real_time_enabled)
push_frontend_feature_flag(:real_time_issue_sidebar, @project, default_enabled: :yaml)
push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml)
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
push_frontend_feature_flag(:labels_widget, @project, default_enabled: :yaml)

View file

@ -3,6 +3,8 @@
class Projects::WikisController < Projects::ApplicationController
include WikiActions
urgency :low
alias_method :container, :project
before_action do

View file

@ -233,6 +233,7 @@ module IssuesHelper
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.try(:id) }),
project_import_jira_path: project_import_jira_path(project),
quick_actions_help_path: help_page_path('user/project/quick_actions'),
releases_path: project_releases_path(project, format: :json),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
show_new_issue_link: show_new_issue_link?(project).to_s
)

View file

@ -73,7 +73,12 @@ module Integrations
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}", required: true }.freeze,
{ type: 'text', name: 'username', placeholder: 'GitLab-integration' }.freeze,
{ type: 'checkbox', name: 'notify_only_broken_pipelines', help: 'Do not send notifications for successful pipelines.' }.freeze,
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }.freeze,
{
type: 'select',
name: 'branches_to_be_notified',
title: s_('Integrations|Branches for which notifications are to be sent'),
choices: branch_choices
}.freeze,
{
type: 'text',
name: 'labels_to_be_notified',

View file

@ -41,7 +41,12 @@ module Integrations
[
{ type: "text", name: "webhook", placeholder: "https://discordapp.com/api/webhooks/…", help: "URL to the webhook for the Discord channel." },
{ type: "checkbox", name: "notify_only_broken_pipelines" },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
{
type: 'select',
name: 'branches_to_be_notified',
title: s_('Integrations|Branches for which notifications are to be sent'),
choices: branch_choices
}
]
end

View file

@ -76,7 +76,12 @@ module Integrations
help: s_("EmailsOnPushService|Send notifications from the committer's email address if the domain matches the domain used by your GitLab instance (such as %{domains}).") % { domains: domains } },
{ type: 'checkbox', name: 'disable_diffs', title: s_("EmailsOnPushService|Disable code diffs"),
help: s_("EmailsOnPushService|Don't include possibly sensitive code diffs in notification body.") },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices },
{
type: 'select',
name: 'branches_to_be_notified',
title: s_('Integrations|Branches for which notifications are to be sent'),
choices: branch_choices
},
{
type: 'textarea',
name: 'recipients',

View file

@ -40,7 +40,12 @@ module Integrations
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
{
type: 'select',
name: 'branches_to_be_notified',
title: s_('Integrations|Branches for which notifications are to be sent'),
choices: branch_choices
}
]
end

View file

@ -37,7 +37,12 @@ module Integrations
[
{ type: 'text', name: 'webhook', placeholder: "#{webhook_placeholder}" },
{ type: 'checkbox', name: 'notify_only_broken_pipelines', help: 'If selected, successful pipelines do not trigger a notification event.' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
{
type: 'select',
name: 'branches_to_be_notified',
title: s_('Integrations|Branches for which notifications are to be sent'),
choices: branch_choices
}
]
end

View file

@ -71,6 +71,7 @@ module Integrations
name: 'notify_only_broken_pipelines' },
{ type: 'select',
name: 'branches_to_be_notified',
title: s_('Integrations|Branches for which notifications are to be sent'),
choices: branch_choices }
]
end

View file

@ -34,7 +34,12 @@ module Integrations
[
{ type: 'text', name: 'webhook', placeholder: "https://yourcircuit.com/rest/v2/webhooks/incoming/…", required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
{
type: 'select',
name: 'branches_to_be_notified',
title: s_('Integrations|Branches for which notifications are to be sent'),
choices: branch_choices
}
]
end

View file

@ -36,7 +36,12 @@ module Integrations
[
{ type: 'text', name: 'webhook', placeholder: "https://api.ciscospark.com/v1/webhooks/incoming/...", required: true },
{ type: 'checkbox', name: 'notify_only_broken_pipelines' },
{ type: 'select', name: 'branches_to_be_notified', choices: branch_choices }
{
type: 'select',
name: 'branches_to_be_notified',
title: s_('Integrations|Branches for which notifications are to be sent'),
choices: branch_choices
}
]
end

View file

@ -80,7 +80,7 @@ module Issues
todo_service.reassigned_assignable(issue, current_user, old_assignees)
track_incident_action(current_user, issue, :incident_assigned)
if Gitlab::ActionCable::Config.in_app? || Feature.enabled?(:broadcast_issue_updates, issue.project)
if Feature.enabled?(:broadcast_issue_updates, issue.project, default_enabled: :yaml)
GraphqlTriggers.issuable_assignees_updated(issue)
end
end

View file

@ -59,8 +59,4 @@ Rails.application.configure do
config.logger = ActiveSupport::TaggedLogging.new(Logger.new(nil))
config.log_level = :fatal
end
# Mount the ActionCable Engine in-app so that we don't have to spawn another Puma
# process for feature specs
ENV['ACTION_CABLE_IN_APP'] = 'true'
end

View file

@ -1,8 +1,8 @@
---
name: broadcast_issue_updates
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30732
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1210
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/3413
milestone: '13.0'
type: development
group: group::project management
default_enabled: false
default_enabled: true

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1210
milestone: '13.0'
type: development
group: group::project management
default_enabled: false
default_enabled: true

View file

@ -65,7 +65,7 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
Gitlab::Metrics::Samplers::DatabaseSampler.initialize_instance.start
Gitlab::Metrics::Samplers::ThreadsSampler.initialize_instance.start
if Gitlab::Runtime.action_cable?
if Gitlab::Runtime.web_server?
Gitlab::Metrics::Samplers::ActionCableSampler.instance.start
end

View file

@ -3,8 +3,7 @@
require 'action_cable/subscription_adapter/redis'
Rails.application.configure do
# Mount the ActionCable engine when in-app mode is enabled
config.action_cable.mount_path = Gitlab::ActionCable::Config.in_app? ? '/-/cable' : nil
config.action_cable.mount_path = '/-/cable'
config.action_cable.url = Gitlab::Utils.append_path(Gitlab.config.gitlab.relative_url_root, '/-/cable')
config.action_cable.worker_pool_size = Gitlab::ActionCable::Config.worker_pool_size

View file

@ -4,24 +4,21 @@
DEFAULT_BRANCH = 'master'
THROUGHPUT_LABELS = [
'Community contribution',
'security',
'bug',
'feature',
TYPE_LABELS = [
'type::feature',
'feature::addition',
'feature::maintenance',
'tooling',
'type::maintenance',
'type::tooling',
'tooling::pipelines',
'tooling::workflow',
'documentation'
'type::bug'
].freeze
if gitlab.mr_body.size < 5
fail "Please provide a proper merge request description."
end
if (THROUGHPUT_LABELS & gitlab.mr_labels).empty?
if (TYPE_LABELS & gitlab.mr_labels).empty?
warn 'Please add a [merge request type](https://about.gitlab.com/handbook/engineering/metrics/#work-type-classification) to this merge request.'
end

View file

@ -8,7 +8,7 @@ SPECIALIZATIONS = {
frontend: 'frontend',
docs: 'documentation',
qa: 'QA',
tooling: 'tooling',
tooling: 'type::tooling',
ci_template: 'ci::templates',
feature_flag: 'feature flag'
}.freeze

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
NO_SPECS_LABELS = [
'tooling',
'type::tooling',
'tooling::pipelines',
'tooling::workflow',
'documentation',

View file

@ -29,7 +29,7 @@ The extensive usage of feature flags poses a few challenges
We sometimes forget how our feature flags are configured or why we haven't
yet removed the feature flag.
- The usage of feature flags can also be confusing to people outside of
development that might not fully understand dependence of ~feature or ~bug
development that might not fully understand dependence of ~"type::feature" or ~"type::bug"
fix on feature flag and how this feature flag is configured. Or if the feature
should be announced as part of release post.
- Maintaining feature flags poses additional challenge of having to manage

View file

@ -78,7 +78,7 @@ duration is capped at 5 seconds.
## Decreasing the urgency (setting a higher target duration)
Increasing the urgency on an existing endpoint can be done on
Decreasing the urgency on an existing endpoint can be done on
a case-by-case basis. Please take the following into account:
1. Apdex is about perceived performance, if a user is actively waiting
@ -137,7 +137,7 @@ the decision.
## Increasing urgency (setting a lower target duration)
When decreasing the target duration, we need to make sure the endpoint
When increasing the urgency, we need to make sure the endpoint
still meets SLO for the fleet that handles the request. You can use the
information in the logs to determine this:

View file

@ -45,7 +45,7 @@ scheduling into milestones. Labeling is a task for everyone. (For some projects,
Most issues will have labels for at least one of the following:
- Type. For example: `~feature`, `~bug`, `~tooling`, or `~documentation`.
- Type. For example: `~"type::feature"`, `~"type::bug"`, or `~"type::tooling"`.
- Stage. For example: `~"devops::plan"` or `~"devops::create"`.
- Group. For example: `~"group::source code"`, `~"group::knowledge"`, or `~"group::editor"`.
- Category. For example: `~"Category:Code Analytics"`, `~"Category:DevOps Reports"`, or `~"Category:Templates"`.
@ -70,12 +70,12 @@ issue should have one and only one.
The current type labels are:
- `~feature`
- `~"type::feature"`
- `~"feature::addition"`
- `~"feature::enhancement"`
- `~"feature::maintenance"`
- `~bug`
- `~tooling`
- `~"type::maintenance"`
- `~"type::bug"`
- `~"type::tooling"`
- `~"tooling::pipelines"`
- `~"tooling::workflow"`
- `~"support request"`
@ -342,11 +342,11 @@ To create a feature proposal, open an issue on the
[issue tracker](https://gitlab.com/gitlab-org/gitlab/-/issues).
In order to help track the feature proposals, we have created a
[`feature`](https://gitlab.com/gitlab-org/gitlab/-/issues?label_name=feature) label.
[`~"type::feature"`](https://gitlab.com/gitlab-org/gitlab/-/issues?label_name=type::feature) label.
For the time being, users that are not members of the project cannot add labels.
You can instead ask one of the [core team](https://about.gitlab.com/community/core-team/)
members to add the label `~feature` to the issue or add the following
code snippet right after your description in a new line: `~feature`.
members to add the label `~"type::feature"` to the issue or add the following
code snippet right after your description in a new line: `~"type::feature"`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.

View file

@ -39,7 +39,7 @@ The following are also added by members of the Technical Writing team:
- The `~Technical Writing` [team label](../contributing/issue_workflow.md#team-labels).
Documentation changes that are not associated with the release of a new or updated feature
do not take the `~feature` label, but still need the `~documentation` label.
do not take the `~"type::feature"` label, but still need the `~documentation` label.
They may include:

View file

@ -180,7 +180,7 @@ subgraph "CNG-mirror pipeline"
**Additional notes:**
- If the `review-deploy` job keeps failing (and a manual retry didn't help),
please post a message in the `#g_qe_engineering_productivity` channel and/or create a `~"Engineering Productivity"` `~"ep::review apps"` `~bug`
please post a message in the `#g_qe_engineering_productivity` channel and/or create a `~"Engineering Productivity"` `~"ep::review apps"` `~"type::bug"`
issue with a link to your merge request. Note that the deployment failure can
reveal an actual problem introduced in your merge request (i.e. this isn't
necessarily a transient failure)!
@ -189,7 +189,7 @@ subgraph "CNG-mirror pipeline"
your merge request. You can also download the artifacts to see screenshots of
the page at the time the failures occurred. If you don't find the cause of the
failure or if it seems unrelated to your change, please post a message in the
`#quality` channel and/or create a ~Quality ~bug issue with a link to your
`#quality` channel and/or create a ~Quality ~"type::bug" issue with a link to your
merge request.
- The manual `review-stop` can be used to
stop a Review App manually, and is also started by GitLab once a merge

View file

@ -54,7 +54,7 @@ Then fill in the integration configuration:
To change the bot's username, provide a value.
- **Notify only broken pipelines**: If you enable the **Pipeline** event, and you want
notifications about failed pipelines only.
- **Branches to be notified**: The branches to send notifications for.
- **Branches for which notifications are to be sent**: The branches to send notifications for.
- **Labels to be notified**: (Optional) Labels required for the issue or merge request
to trigger a notification. Leave blank to notify for all issues and merge requests.
- **Labels to be notified behavior**: When you use the **Labels to be notified** filter,

View file

@ -45,7 +45,7 @@ to control GitLab from Slack. Slash commands are configured separately.
1. (Optional) In **Username**, enter the username of the Slack bot that sends
the notifications.
1. Select the **Notify only broken pipelines** checkbox to notify only on failures.
1. In the **Branches to be notified** dropdown, select which types of branches
1. In the **Branches for which notifications are to be sent** dropdown, select which types of branches
to send notifications for.
1. Leave the **Labels to be notified** field blank to get all notifications, or
add labels that the issue or merge request must have to trigger a

View file

@ -21,7 +21,7 @@ In GitLab:
1. Select the checkboxes corresponding to the GitLab events you want to receive in Unify Circuit.
1. Paste the **Webhook URL** that you copied from the Unify Circuit configuration step.
1. Select the **Notify only broken pipelines** checkbox to notify only on failures.
1. In the **Branches to be notified** dropdown, select which types of branches to send notifications for.
1. In the **Branches for which notifications are to be sent** dropdown, select which types of branches to send notifications for.
1. Select `Save changes` or optionally select **Test settings**.
Your Unify Circuit conversation now starts receiving GitLab event notifications.

View file

@ -380,12 +380,18 @@ You can also use the `/iteration`
[quick action](../quick_actions.md#issues-merge-requests-and-epics)
in a comment or description field.
## Real-time sidebar **(FREE SELF)**
## Real-time sidebar
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17589) in GitLab 13.3.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17589) in GitLab 13.3. Disabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/3413) in GitLab 13.9.
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/17589) in GitLab 14.5.
Assignees in the sidebar are updated in real time. This feature is **disabled by default**.
To enable it, you need to enable [Action Cable in-app mode](https://docs.gitlab.com/omnibus/settings/actioncable.html).
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature per project or for your entire instance, ask an administrator to
[disable the feature flags](../../../administration/feature_flags.md) named `real_time_issue_sidebar` and `broadcast_issue_updates`.
On GitLab.com, this feature is available.
Assignees in the sidebar are updated in real time.
## Similar issues

View file

@ -43,7 +43,7 @@ module API
params do
use :pagination
end
get 'public' do
get 'public', urgency: :low do
authenticate!
present paginate(public_snippets), with: Entities::PersonalSnippet, current_user: current_user

View file

@ -32,7 +32,7 @@ module API
params do
optional :with_content, type: Boolean, default: false, desc: "Include pages' content"
end
get ':id/wikis' do
get ':id/wikis', urgency: :low do
authorize! :read_wiki, container
entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic

View file

@ -4,10 +4,6 @@ module Gitlab
module ActionCable
class Config
class << self
def in_app?
Gitlab::Utils.to_boolean(ENV.fetch('ACTION_CABLE_IN_APP', false))
end
def worker_pool_size
ENV.fetch('ACTION_CABLE_WORKER_POOL_SIZE', 4).to_i
end

View file

@ -39,23 +39,14 @@ module Gitlab
def sample
pool = @action_cable.worker_pool.executor
labels = {
server_mode: server_mode
}
metrics[:active_connections].set(labels, @action_cable.connections.size)
metrics[:pool_min_size].set(labels, pool.min_length)
metrics[:pool_max_size].set(labels, pool.max_length)
metrics[:pool_current_size].set(labels, pool.length)
metrics[:pool_largest_size].set(labels, pool.largest_length)
metrics[:pool_completed_tasks].set(labels, pool.completed_task_count)
metrics[:pool_pending_tasks].set(labels, pool.queue_length)
end
private
def server_mode
Gitlab::ActionCable::Config.in_app? ? 'in-app' : 'standalone'
metrics[:active_connections].set({}, @action_cable.connections.size)
metrics[:pool_min_size].set({}, pool.min_length)
metrics[:pool_max_size].set({}, pool.max_length)
metrics[:pool_current_size].set({}, pool.length)
metrics[:pool_largest_size].set({}, pool.largest_length)
metrics[:pool_completed_tasks].set({}, pool.completed_task_count)
metrics[:pool_pending_tasks].set({}, pool.queue_length)
end
end
end

View file

@ -63,12 +63,8 @@ module Gitlab
puma?
end
def action_cable?
web_server? && (!!defined?(ACTION_CABLE_SERVER) || Gitlab::ActionCable::Config.in_app?)
end
def multi_threaded?
puma? || sidekiq? || action_cable?
puma? || sidekiq?
end
def puma_in_clustered_mode?
@ -92,7 +88,7 @@ module Gitlab
threads += Sidekiq.options[:concurrency] + 2
end
if action_cable?
if web_server?
threads += Gitlab::ActionCable::Config.worker_pool_size
end

View file

@ -18350,6 +18350,9 @@ msgstr ""
msgid "Integrations|An error occurred while loading projects using custom settings."
msgstr ""
msgid "Integrations|Branches for which notifications are to be sent"
msgstr ""
msgid "Integrations|Browser limitations"
msgstr ""
@ -34507,6 +34510,9 @@ msgstr ""
msgid "There was a problem fetching recent projects."
msgstr ""
msgid "There was a problem fetching releases."
msgstr ""
msgid "There was a problem fetching the job token scope value"
msgstr ""

View file

@ -1084,28 +1084,30 @@ RSpec.describe Projects::IssuesController do
end
context 'real-time sidebar feature flag' do
using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }
where(:action_cable_in_app_enabled, :feature_flag_enabled, :gon_feature_flag) do
true | true | true
true | false | true
false | true | true
false | false | false
end
with_them do
context 'when enabled' do
before do
expect(Gitlab::ActionCable::Config).to receive(:in_app?).and_return(action_cable_in_app_enabled)
stub_feature_flags(real_time_issue_sidebar: feature_flag_enabled)
stub_feature_flags(real_time_issue_sidebar: true)
end
it 'broadcasts to the issues channel based on ActionCable and feature flag values' do
it 'pushes the correct value to the frontend' do
go(id: issue.to_param)
expect(Gon.features).to include('realTimeIssueSidebar' => gon_feature_flag)
expect(Gon.features).to include('realTimeIssueSidebar' => true)
end
end
context 'when disabled' do
before do
stub_feature_flags(real_time_issue_sidebar: false)
end
it 'pushes the correct value to the frontend' do
go(id: issue.to_param)
expect(Gon.features).to include('realTimeIssueSidebar' => false)
end
end
end

View file

@ -37,6 +37,7 @@ import {
TOKEN_TYPE_LABEL,
TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_MY_REACTION,
TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE,
TOKEN_TYPE_WEIGHT,
urlSortParams,
@ -581,6 +582,7 @@ describe('IssuesListApp component', () => {
{ type: TOKEN_TYPE_MILESTONE },
{ type: TOKEN_TYPE_LABEL },
{ type: TOKEN_TYPE_TYPE },
{ type: TOKEN_TYPE_RELEASE },
{ type: TOKEN_TYPE_MY_REACTION },
{ type: TOKEN_TYPE_CONFIDENTIAL },
{ type: TOKEN_TYPE_ITERATION },

View file

@ -95,12 +95,18 @@ export const locationSearch = [
'assignee_username[]=lisa',
'not[assignee_username][]=patty',
'not[assignee_username][]=selma',
'milestone_title=season+3',
'milestone_title=season+4',
'not[milestone_title]=season+20',
'not[milestone_title]=season+30',
'label_name[]=cartoon',
'label_name[]=tv',
'not[label_name][]=live action',
'not[label_name][]=drama',
'release_tag=v3',
'release_tag=v4',
'not[release_tag]=v20',
'not[release_tag]=v30',
'type[]=issue',
'type[]=feature',
'not[type][]=bug',
@ -109,7 +115,9 @@ export const locationSearch = [
'not[my_reaction_emoji]=thumbsdown',
'confidential=yes',
'iteration_id=4',
'iteration_id=12',
'not[iteration_id]=20',
'not[iteration_id]=42',
'epic_id=12',
'not[epic_id]=34',
'weight=1',
@ -122,6 +130,7 @@ export const locationSearchWithSpecialValues = [
'my_reaction_emoji=None',
'iteration_id=Current',
'label_name[]=None',
'release_tag=None',
'milestone_title=Upcoming',
'epic_id=None',
'weight=None',
@ -134,12 +143,18 @@ export const filteredTokens = [
{ type: 'assignee_username', value: { data: 'lisa', operator: OPERATOR_IS } },
{ type: 'assignee_username', value: { data: 'patty', operator: OPERATOR_IS_NOT } },
{ type: 'assignee_username', value: { data: 'selma', operator: OPERATOR_IS_NOT } },
{ type: 'milestone', value: { data: 'season 3', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'season 4', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'season 20', operator: OPERATOR_IS_NOT } },
{ type: 'milestone', value: { data: 'season 30', operator: OPERATOR_IS_NOT } },
{ type: 'labels', value: { data: 'cartoon', operator: OPERATOR_IS } },
{ type: 'labels', value: { data: 'tv', operator: OPERATOR_IS } },
{ type: 'labels', value: { data: 'live action', operator: OPERATOR_IS_NOT } },
{ type: 'labels', value: { data: 'drama', operator: OPERATOR_IS_NOT } },
{ type: 'release', value: { data: 'v3', operator: OPERATOR_IS } },
{ type: 'release', value: { data: 'v4', operator: OPERATOR_IS } },
{ type: 'release', value: { data: 'v20', operator: OPERATOR_IS_NOT } },
{ type: 'release', value: { data: 'v30', operator: OPERATOR_IS_NOT } },
{ type: 'type', value: { data: 'issue', operator: OPERATOR_IS } },
{ type: 'type', value: { data: 'feature', operator: OPERATOR_IS } },
{ type: 'type', value: { data: 'bug', operator: OPERATOR_IS_NOT } },
@ -148,7 +163,9 @@ export const filteredTokens = [
{ type: 'my_reaction_emoji', value: { data: 'thumbsdown', operator: OPERATOR_IS_NOT } },
{ type: 'confidential', value: { data: 'yes', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: '4', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: '12', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: '20', operator: OPERATOR_IS_NOT } },
{ type: 'iteration', value: { data: '42', operator: OPERATOR_IS_NOT } },
{ type: 'epic_id', value: { data: '12', operator: OPERATOR_IS } },
{ type: 'epic_id', value: { data: '34', operator: OPERATOR_IS_NOT } },
{ type: 'weight', value: { data: '1', operator: OPERATOR_IS } },
@ -163,6 +180,7 @@ export const filteredTokensWithSpecialValues = [
{ type: 'my_reaction_emoji', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'iteration', value: { data: 'Current', operator: OPERATOR_IS } },
{ type: 'labels', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'release', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'milestone', value: { data: 'Upcoming', operator: OPERATOR_IS } },
{ type: 'epic_id', value: { data: 'None', operator: OPERATOR_IS } },
{ type: 'weight', value: { data: 'None', operator: OPERATOR_IS } },
@ -171,22 +189,24 @@ export const filteredTokensWithSpecialValues = [
export const apiParams = {
authorUsername: 'homer',
assigneeUsernames: ['bart', 'lisa'],
milestoneTitle: 'season 4',
milestoneTitle: ['season 3', 'season 4'],
labelName: ['cartoon', 'tv'],
releaseTag: ['v3', 'v4'],
types: ['ISSUE', 'FEATURE'],
myReactionEmoji: 'thumbsup',
confidential: true,
iterationId: '4',
iterationId: ['4', '12'],
epicId: '12',
weight: '1',
not: {
authorUsername: 'marge',
assigneeUsernames: ['patty', 'selma'],
milestoneTitle: 'season 20',
milestoneTitle: ['season 20', 'season 30'],
labelName: ['live action', 'drama'],
releaseTag: ['v20', 'v30'],
types: ['BUG', 'INCIDENT'],
myReactionEmoji: 'thumbsdown',
iterationId: '20',
iterationId: ['20', '42'],
epicId: '34',
weight: '3',
},
@ -197,6 +217,7 @@ export const apiParamsWithSpecialValues = {
assigneeUsernames: 'bart',
labelName: 'None',
myReactionEmoji: 'None',
releaseTagWildcardId: 'NONE',
iterationWildcardId: 'CURRENT',
milestoneWildcardId: 'UPCOMING',
epicId: 'None',
@ -208,17 +229,19 @@ export const urlParams = {
'not[author_username]': 'marge',
'assignee_username[]': ['bart', 'lisa'],
'not[assignee_username][]': ['patty', 'selma'],
milestone_title: 'season 4',
'not[milestone_title]': 'season 20',
milestone_title: ['season 3', 'season 4'],
'not[milestone_title]': ['season 20', 'season 30'],
'label_name[]': ['cartoon', 'tv'],
'not[label_name][]': ['live action', 'drama'],
release_tag: ['v3', 'v4'],
'not[release_tag]': ['v20', 'v30'],
'type[]': ['issue', 'feature'],
'not[type][]': ['bug', 'incident'],
my_reaction_emoji: 'thumbsup',
'not[my_reaction_emoji]': 'thumbsdown',
confidential: 'yes',
iteration_id: '4',
'not[iteration_id]': '20',
iteration_id: ['4', '12'],
'not[iteration_id]': ['20', '42'],
epic_id: '12',
'not[epic_id]': '34',
weight: '1',
@ -229,6 +252,7 @@ export const urlParamsWithSpecialValues = {
assignee_id: '123',
'assignee_username[]': 'bart',
'label_name[]': 'None',
release_tag: 'None',
my_reaction_emoji: 'None',
iteration_id: 'Current',
milestone_title: 'Upcoming',

View file

@ -58,10 +58,10 @@ describe('getDueDateValue', () => {
describe('getSortOptions', () => {
describe.each`
hasIssueWeightsFeature | hasBlockedIssuesFeature | length | containsWeight | containsBlocking
${false} | ${false} | ${8} | ${false} | ${false}
${true} | ${false} | ${9} | ${true} | ${false}
${false} | ${true} | ${9} | ${false} | ${true}
${true} | ${true} | ${10} | ${true} | ${true}
${false} | ${false} | ${9} | ${false} | ${false}
${true} | ${false} | ${10} | ${true} | ${false}
${false} | ${true} | ${10} | ${false} | ${true}
${true} | ${true} | ${11} | ${true} | ${true}
`(
'when hasIssueWeightsFeature=$hasIssueWeightsFeature and hasBlockedIssuesFeature=$hasBlockedIssuesFeature',
({

View file

@ -1,4 +1,4 @@
import { GlBadge, GlLink, GlIcon, GlDropdown } from '@gitlab/ui';
import { GlBadge, GlLink, GlIcon, GlButton, GlDropdown } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Vue, { nextTick } from 'vue';
@ -947,6 +947,9 @@ describe('MrWidgetOptions', () => {
// Renders a link in the row
expect(collapsedSection.find(GlLink).exists()).toBe(true);
expect(collapsedSection.find(GlLink).text()).toBe('GitLab.com');
expect(collapsedSection.find(GlButton).exists()).toBe(true);
expect(collapsedSection.find(GlButton).text()).toBe('Full report');
});
});
});

View file

@ -31,6 +31,7 @@ export default {
href: 'https://gitlab.com',
text: 'GitLab.com',
},
actions: [{ text: 'Full report', href: 'https://gitlab.com', target: '_blank' }],
},
]);
},

View file

@ -9,6 +9,7 @@ import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_t
import IterationToken from '~/vue_shared/components/filtered_search_bar/tokens/iteration_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue';
export const mockAuthor1 = {
@ -132,6 +133,14 @@ export const mockMilestoneToken = {
fetchMilestones: () => Promise.resolve({ data: mockMilestones }),
};
export const mockReleaseToken = {
type: 'release',
icon: 'rocket',
title: 'Release',
token: ReleaseToken,
fetchReleases: () => Promise.resolve(),
};
export const mockEpicToken = {
type: 'epic_iid',
icon: 'clock',

View file

@ -0,0 +1,78 @@
import { GlFilteredSearchToken, GlFilteredSearchTokenSegment } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue';
import { mockReleaseToken } from '../mock_data';
jest.mock('~/flash');
describe('ReleaseToken', () => {
const id = 123;
let wrapper;
const createComponent = ({ config = mockReleaseToken, value = { data: '' } } = {}) =>
mount(ReleaseToken, {
propsData: {
active: false,
config,
value,
},
provide: {
portalName: 'fake target',
alignSuggestions: function fakeAlignSuggestions() {},
suggestionsListClass: () => 'custom-class',
},
});
afterEach(() => {
wrapper.destroy();
});
it('renders release value', async () => {
wrapper = createComponent({ value: { data: id } });
await wrapper.vm.$nextTick();
const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment);
expect(tokenSegments).toHaveLength(3); // `Release` `=` `v1`
expect(tokenSegments.at(2).text()).toBe(id.toString());
});
it('fetches initial values', () => {
const fetchReleasesSpy = jest.fn().mockResolvedValue();
wrapper = createComponent({
config: { ...mockReleaseToken, fetchReleases: fetchReleasesSpy },
value: { data: id },
});
expect(fetchReleasesSpy).toHaveBeenCalledWith(id);
});
it('fetches releases on user input', () => {
const search = 'hello';
const fetchReleasesSpy = jest.fn().mockResolvedValue();
wrapper = createComponent({
config: { ...mockReleaseToken, fetchReleases: fetchReleasesSpy },
});
wrapper.findComponent(GlFilteredSearchToken).vm.$emit('input', { data: search });
expect(fetchReleasesSpy).toHaveBeenCalledWith(search);
});
it('renders error message when request fails', async () => {
const fetchReleasesSpy = jest.fn().mockRejectedValue();
wrapper = createComponent({
config: { ...mockReleaseToken, fetchReleases: fetchReleasesSpy },
});
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
message: 'There was a problem fetching releases.',
});
});
});

View file

@ -326,6 +326,7 @@ RSpec.describe IssuesHelper do
new_issue_path: new_project_issue_path(project, issue: { milestone_id: finder.milestones.first.id }),
project_import_jira_path: project_import_jira_path(project),
quick_actions_help_path: help_page_path('user/project/quick_actions'),
releases_path: project_releases_path(project, format: :json),
reset_path: new_issuable_address_project_path(project, issuable_type: 'issue'),
rss_path: '#',
show_new_issue_link: 'true',

View file

@ -23,64 +23,46 @@ RSpec.describe Gitlab::Metrics::Samplers::ActionCableSampler do
allow(pool).to receive(:queue_length).and_return(6)
end
shared_examples 'collects metrics' do |expected_labels|
it 'includes active connections' do
expect(subject.metrics[:active_connections]).to receive(:set).with(expected_labels, 0)
it 'includes active connections' do
expect(subject.metrics[:active_connections]).to receive(:set).with({}, 0)
subject.sample
end
it 'includes minimum worker pool size' do
expect(subject.metrics[:pool_min_size]).to receive(:set).with(expected_labels, 1)
subject.sample
end
it 'includes maximum worker pool size' do
expect(subject.metrics[:pool_max_size]).to receive(:set).with(expected_labels, 2)
subject.sample
end
it 'includes current worker pool size' do
expect(subject.metrics[:pool_current_size]).to receive(:set).with(expected_labels, 3)
subject.sample
end
it 'includes largest worker pool size' do
expect(subject.metrics[:pool_largest_size]).to receive(:set).with(expected_labels, 4)
subject.sample
end
it 'includes worker pool completed task count' do
expect(subject.metrics[:pool_completed_tasks]).to receive(:set).with(expected_labels, 5)
subject.sample
end
it 'includes worker pool pending task count' do
expect(subject.metrics[:pool_pending_tasks]).to receive(:set).with(expected_labels, 6)
subject.sample
end
subject.sample
end
context 'for in-app mode' do
before do
expect(Gitlab::ActionCable::Config).to receive(:in_app?).and_return(true)
end
it 'includes minimum worker pool size' do
expect(subject.metrics[:pool_min_size]).to receive(:set).with({}, 1)
it_behaves_like 'collects metrics', server_mode: 'in-app'
subject.sample
end
context 'for standalone mode' do
before do
expect(Gitlab::ActionCable::Config).to receive(:in_app?).and_return(false)
end
it 'includes maximum worker pool size' do
expect(subject.metrics[:pool_max_size]).to receive(:set).with({}, 2)
it_behaves_like 'collects metrics', server_mode: 'standalone'
subject.sample
end
it 'includes current worker pool size' do
expect(subject.metrics[:pool_current_size]).to receive(:set).with({}, 3)
subject.sample
end
it 'includes largest worker pool size' do
expect(subject.metrics[:pool_largest_size]).to receive(:set).with({}, 4)
subject.sample
end
it 'includes worker pool completed task count' do
expect(subject.metrics[:pool_completed_tasks]).to receive(:set).with({}, 5)
subject.sample
end
it 'includes worker pool pending task count' do
expect(subject.metrics[:pool_pending_tasks]).to receive(:set).with({}, 6)
subject.sample
end
end
end

View file

@ -48,10 +48,9 @@ RSpec.describe Gitlab::Runtime do
before do
stub_const('::Puma', puma_type)
stub_env('ACTION_CABLE_IN_APP', 'false')
end
it_behaves_like "valid runtime", :puma, 1
it_behaves_like "valid runtime", :puma, 1 + Gitlab::ActionCable::Config.worker_pool_size
end
context "puma with cli_config" do
@ -61,27 +60,16 @@ RSpec.describe Gitlab::Runtime do
before do
stub_const('::Puma', puma_type)
allow(puma_type).to receive_message_chain(:cli_config, :options).and_return(max_threads: 2, workers: max_workers)
stub_env('ACTION_CABLE_IN_APP', 'false')
end
it_behaves_like "valid runtime", :puma, 3
it_behaves_like "valid runtime", :puma, 3 + Gitlab::ActionCable::Config.worker_pool_size
context "when ActionCable in-app mode is enabled" do
context "when ActionCable worker pool size is configured" do
before do
stub_env('ACTION_CABLE_IN_APP', 'true')
stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '3')
stub_env('ACTION_CABLE_WORKER_POOL_SIZE', 10)
end
it_behaves_like "valid runtime", :puma, 6
end
context "when ActionCable standalone is run" do
before do
stub_const('ACTION_CABLE_SERVER', true)
stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '8')
end
it_behaves_like "valid runtime", :puma, 11
it_behaves_like "valid runtime", :puma, 13
end
describe ".puma_in_clustered_mode?" do

View file

@ -86,7 +86,7 @@ RSpec.describe ServiceFieldEntity do
expected_hash = {
type: 'select',
name: 'branches_to_be_notified',
title: nil,
title: 'Branches for which notifications are to be sent',
placeholder: nil,
required: nil,
choices: [['All branches', 'all'], ['Default branch', 'default'], ['Protected branches', 'protected'], ['Default branch and protected branches', 'default_and_protected']],

View file

@ -1248,28 +1248,38 @@ RSpec.describe Issues::UpdateService, :mailer do
let(:closed_issuable) { create(:closed_issue, project: project) }
end
context 'real-time updates' do
using RSpec::Parameterized::TableSyntax
context 'broadcasting issue assignee updates' do
let(:update_params) { { assignee_ids: [user2.id] } }
where(:action_cable_in_app_enabled, :feature_flag_enabled, :should_broadcast) do
true | true | true
true | false | true
false | true | true
false | false | false
context 'when feature flag is enabled' do
before do
stub_feature_flags(broadcast_issue_updates: true)
end
it 'triggers the GraphQL subscription' do
expect(GraphqlTriggers).to receive(:issuable_assignees_updated).with(issue)
update_issue(update_params)
end
context 'when assignee is not updated' do
let(:update_params) { { title: 'Some other title' } }
it 'does not trigger the GraphQL subscription' do
expect(GraphqlTriggers).not_to receive(:issuable_assignees_updated).with(issue)
update_issue(update_params)
end
end
end
with_them do
it 'broadcasts to the issues channel based on ActionCable and feature flag values' do
allow(Gitlab::ActionCable::Config).to receive(:in_app?).and_return(action_cable_in_app_enabled)
stub_feature_flags(broadcast_issue_updates: feature_flag_enabled)
context 'when feature flag is disabled' do
before do
stub_feature_flags(broadcast_issue_updates: false)
end
if should_broadcast
expect(GraphqlTriggers).to receive(:issuable_assignees_updated).with(issue)
else
expect(GraphqlTriggers).not_to receive(:issuable_assignees_updated).with(issue)
end
it 'does not trigger the GraphQL subscription' do
expect(GraphqlTriggers).not_to receive(:issuable_assignees_updated).with(issue)
update_issue(update_params)
end

View file

@ -228,7 +228,7 @@ RSpec.describe Tooling::Danger::Changelog do
end
context 'with changelog label' do
let(:mr_labels) { ['feature'] }
let(:mr_labels) { ['type::feature'] }
it 'is truthy' do
is_expected.to be_truthy
@ -236,7 +236,7 @@ RSpec.describe Tooling::Danger::Changelog do
end
context 'with no changelog label' do
let(:mr_labels) { ['tooling'] }
let(:mr_labels) { ['type::tooling'] }
it 'is truthy' do
is_expected.to be_falsey

View file

@ -6,7 +6,7 @@ module Tooling
module Danger
module Changelog
NO_CHANGELOG_LABELS = [
'tooling',
'type::tooling',
'tooling::pipelines',
'tooling::workflow',
'ci-build',