Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-09 21:07:34 +00:00
parent 20f6a17ba2
commit 6bed1b9c9c
62 changed files with 657 additions and 459 deletions

View File

@ -146,7 +146,7 @@ gem 'fog-aws', '~> 3.15'
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
# Also see config/initializers/fog_core_patch.rb.
gem 'fog-core', '= 2.1.0'
gem 'fog-google', '~> 1.15', require: 'fog/google'
gem 'fog-google', '~> 1.19', require: 'fog/google'
gem 'fog-local', '~> 0.8'
gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1'
@ -551,6 +551,7 @@ gem 'valid_email', '~> 0.1'
gem 'json', '~> 2.5.1'
gem 'json_schemer', '~> 0.2.18'
gem 'oj', '~> 3.13.21'
gem 'oj-introspect', '~> 0.7'
gem 'multi_json', '~> 1.14.1'
gem 'yajl-ruby', '~> 1.4.3', require: 'yajl'

View File

@ -182,7 +182,7 @@
{"name":"fog-aliyun","version":"0.3.3","platform":"ruby","checksum":"d0aa317f7c1473a1d684fff51699f216bb9cb78b9ee9ce55a81c9bcc93fb85ee"},
{"name":"fog-aws","version":"3.15.0","platform":"ruby","checksum":"09752931ea0c6165b018e1a89253248d86b246645086ccf19bc44fabe3381e8c"},
{"name":"fog-core","version":"2.1.0","platform":"ruby","checksum":"53e5d793554d7080d015ef13cd44b54027e421d924d9dba4ce3d83f95f37eda9"},
{"name":"fog-google","version":"1.15.0","platform":"ruby","checksum":"2f840780fbf2384718e961b05ef2fc522b4213bbda6f25b28c1bbd875ff0b306"},
{"name":"fog-google","version":"1.19.0","platform":"ruby","checksum":"3c909a230837fe84117fffdfd927b523821b88f61d3aeab531e1417a9810f488"},
{"name":"fog-json","version":"1.2.0","platform":"ruby","checksum":"dd4f5ab362dbc72b687240bba9d2dd841d5dfe888a285797533f85c03ea548fe"},
{"name":"fog-local","version":"0.8.0","platform":"ruby","checksum":"263b2d09e54c69d1b87ad7f235a1a1e53c8a674edcedf7512c1715765ad7ef79"},
{"name":"fog-openstack","version":"1.0.8","platform":"ruby","checksum":"8f174ab5e5b1bc107c7da90cc7c47a24930e1566cd88ab4df447026ea8b63d9c"},
@ -193,6 +193,7 @@
{"name":"fuubar","version":"2.2.0","platform":"ruby","checksum":"9b0263c4074f39c68b37f1e4e69a7d3cfc7523c41bea43601235daa723179b4a"},
{"name":"fuzzyurl","version":"0.9.0","platform":"ruby","checksum":"542efa80f2bcaadbdc402c2f0b572f2e335a1d53e375aecad68bbb3d86860c0f"},
{"name":"gemoji","version":"3.0.1","platform":"ruby","checksum":"80553f2f4932a7a95fb1b3c7c63f7dd937e7c8c610164bbdea28fd06eba5f36d"},
{"name":"gems","version":"1.2.0","platform":"ruby","checksum":"343d74bd54d906f38193f3ccd983f9d08c4b54cd01ee7e5fe8467ab41a9946f0"},
{"name":"get_process_mem","version":"0.2.7","platform":"ruby","checksum":"4afd3c3641dd6a817c09806c7d6d509d8a9984512ac38dea8b917426bbf77eba"},
{"name":"gettext","version":"3.3.6","platform":"ruby","checksum":"ee6bbd1b2f833ee52d7797fa68acbfecc4726aec6b6280fd7eab92aa0190b413"},
{"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"},
@ -216,7 +217,17 @@
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
{"name":"globalid","version":"1.0.0","platform":"ruby","checksum":"1253641b1dc3392721c964351773755d75135d3d3c5cc65d88b0a3880a60bed8"},
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
{"name":"google-api-client","version":"0.50.0","platform":"ruby","checksum":"3ae45e972f293f3a66e53950ecc0fd350d85d6347c06a430bb971bd1ae5ad617"},
{"name":"google-api-client","version":"0.53.0","platform":"ruby","checksum":"41006ef21fe02a70cff39a10aebf84fa7fb5f24c63566ab12b149ff1f1d9d7ff"},
{"name":"google-apis-compute_v1","version":"0.53.0","platform":"ruby","checksum":"629537cf9efc1aeda0bb00d78c2a6ffa8488de833a8b19bdb150ce0a6a105f4b"},
{"name":"google-apis-core","version":"0.9.1","platform":"ruby","checksum":"c012a364891a4602b4b1aa8468400dd3fa50b00e694edb4411af6b85aa3eb034"},
{"name":"google-apis-discovery_v1","version":"0.12.0","platform":"ruby","checksum":"2e5accfe126884e5ebd8540b3a17a878a3a050d0dfdf0ece6b231846fc485a15"},
{"name":"google-apis-dns_v1","version":"0.28.0","platform":"ruby","checksum":"f523631ea2737b67096e21eff25e426edb51ffefa9979a42f798936a950df34c"},
{"name":"google-apis-generator","version":"0.11.0","platform":"ruby","checksum":"4656febed121b21e9071118c79ab67cbec9e40a39b6a38acc05d07fafa321279"},
{"name":"google-apis-iamcredentials_v1","version":"0.15.0","platform":"ruby","checksum":"e9a256a6d80fbfc77d44bd7e65bc94b9e1e9863a00e6d413edc0102d6cb5551b"},
{"name":"google-apis-monitoring_v3","version":"0.37.0","platform":"ruby","checksum":"2d9262ae8dfa83ac7db895b03c7deeaae9f13107e94c8781a432202fbc20736a"},
{"name":"google-apis-pubsub_v1","version":"0.30.0","platform":"ruby","checksum":"b8905915388041bf54f9b7e988c8cc64fe00c2132475d5c753d10479415ee13d"},
{"name":"google-apis-sqladmin_v1beta4","version":"0.38.0","platform":"ruby","checksum":"d00279cdcc5548bf4f4e40cc29cbd942b79708011e59c75a18726b6826be1665"},
{"name":"google-apis-storage_v1","version":"0.20.0","platform":"ruby","checksum":"8a1ace07fc909966d6f76e777d6adc7d86dddd91a629fef8914ebd5baf86d850"},
{"name":"google-cloud-env","version":"1.6.0","platform":"ruby","checksum":"6179acb946975892c7908748df5722a4ebadfc8cf5bb7b0d8d933ca67183fa15"},
{"name":"google-protobuf","version":"3.21.9","platform":"java","checksum":"8483ab2487170434f7a139d6534b3a166e4ec244a6fd8929f758d87abbb82fee"},
{"name":"google-protobuf","version":"3.21.9","platform":"ruby","checksum":"5a656c159aa2c85008af7eab3f603cf22921b748e09438f6682dcf696d518adc"},
@ -227,7 +238,7 @@
{"name":"google-protobuf","version":"3.21.9","platform":"x86_64-darwin","checksum":"9e948a08ee27cca8acf794c798db16d918ce503eae06525d7551dc05ac3324c0"},
{"name":"google-protobuf","version":"3.21.9","platform":"x86_64-linux","checksum":"d4053012022f7bf47cd54c7c19416f600325e6cc1e1604a631c2fde69dd920a4"},
{"name":"googleapis-common-protos-types","version":"1.3.0","platform":"ruby","checksum":"c5411f3197cc3e02547ded1858303b1f830b4dc89c588c142ad6c8a231050671"},
{"name":"googleauth","version":"0.14.0","platform":"ruby","checksum":"4659b563d5b2727e775ba9231e75485c1b55ac8fc319e0bf1bc87d5e9705a632"},
{"name":"googleauth","version":"1.3.0","platform":"ruby","checksum":"51dd7362353cf1e90a2d01e1fb94321ae3926c776d4dc4a79db65230217ffcc2"},
{"name":"gpgme","version":"2.0.20","platform":"ruby","checksum":"fc194689cff40cd4ccafb3086031e930650b3efc15348bbfdf7a2f8b5a826f75"},
{"name":"grape","version":"1.5.2","platform":"ruby","checksum":"1df3b734c3862e235174232bc629587eddda9ef3df648230827575186700ae29"},
{"name":"grape-entity","version":"0.10.0","platform":"ruby","checksum":"9aed1e7cbbc96d9e73f72e5f32c776d4ba8a5baf54c3acda2682008dba2b2cfe"},
@ -372,7 +383,8 @@
{"name":"oauth2","version":"2.0.9","platform":"ruby","checksum":"b21f9defcf52dc1610e0dfab4c868342173dcd707fd15c777d9f4f04e153f7fb"},
{"name":"octokit","version":"4.25.1","platform":"ruby","checksum":"c02092ee82dcdfe84db0e0ea630a70d32becc54245a4f0bacfd21c010df09b96"},
{"name":"ohai","version":"16.10.6","platform":"ruby","checksum":"b835806e585faea4ac8346b68c722fb5fc29a29f73fd7e3a022f9073132dec22"},
{"name":"oj","version":"3.13.21","platform":"ruby","checksum":"aef31a8dcc6f0b9b4bb5cc7ac6cc5272b2d851deb11a1804c2ed6b5501b50e46"},
{"name":"oj","version":"3.13.23","platform":"ruby","checksum":"206dfdc4020ad9974705037f269cfba211d61b7662a58c717cce771829ccef51"},
{"name":"oj-introspect","version":"0.7.0","platform":"ruby","checksum":"dacd2504fedf67ed26733efa753e3b4da1888ad2a9752e81948acb8a196b8420"},
{"name":"omniauth","version":"2.1.0","platform":"ruby","checksum":"bff7234f5ec9323622b217c7f26d52f850de0b0e2b8c807c3358fc79fe572300"},
{"name":"omniauth-alicloud","version":"2.0.0","platform":"ruby","checksum":"8ecf369d51cd5317c1e7c6b80276891f76cff210a534ec654326af5c62265de3"},
{"name":"omniauth-atlassian-oauth2","version":"0.2.0","platform":"ruby","checksum":"eb07574a188ab8a03376ce288bce86bc2dd4a1382ffa5781cb5e2b7bc15d76c9"},

View File

@ -501,11 +501,17 @@ GEM
excon (~> 0.58)
formatador (~> 0.2)
mime-types
fog-google (1.15.0)
fog-core (<= 2.1.0)
fog-google (1.19.0)
fog-core (< 2.3)
fog-json (~> 1.2)
fog-xml (~> 0.1.0)
google-api-client (>= 0.44.2, < 0.51)
google-apis-compute_v1 (~> 0.14)
google-apis-dns_v1 (~> 0.12)
google-apis-iamcredentials_v1 (~> 0.6)
google-apis-monitoring_v3 (~> 0.12)
google-apis-pubsub_v1 (~> 0.7)
google-apis-sqladmin_v1beta4 (~> 0.13)
google-apis-storage_v1 (~> 0.6)
google-cloud-env (~> 1.2)
fog-json (1.2.0)
fog-core
@ -533,6 +539,7 @@ GEM
ruby-progressbar (~> 1.4)
fuzzyurl (0.9.0)
gemoji (3.0.1)
gems (1.2.0)
get_process_mem (0.2.7)
ffi (~> 1.0)
gettext (3.3.6)
@ -608,27 +615,52 @@ GEM
i18n (>= 0.7)
multi_json
request_store (>= 1.0)
google-api-client (0.50.0)
google-api-client (0.53.0)
google-apis-core (~> 0.1)
google-apis-generator (~> 0.1)
google-apis-compute_v1 (0.53.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-core (0.9.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
retriable (>= 2.0, < 4.a)
rexml
signet (~> 0.12)
webrick
google-apis-discovery_v1 (0.12.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-dns_v1 (0.28.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-generator (0.11.0)
activesupport (>= 5.0)
gems (~> 1.2)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-discovery_v1 (~> 0.5)
thor (>= 0.20, < 2.a)
google-apis-iamcredentials_v1 (0.15.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-monitoring_v3 (0.37.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-pubsub_v1 (0.30.0)
google-apis-core (>= 0.9.1, < 2.a)
google-apis-sqladmin_v1beta4 (0.38.0)
google-apis-core (>= 0.9.0, < 2.a)
google-apis-storage_v1 (0.20.0)
google-apis-core (>= 0.9.1, < 2.a)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-protobuf (3.21.9)
googleapis-common-protos-types (1.3.0)
google-protobuf (~> 3.14)
googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0)
googleauth (1.3.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.14)
signet (>= 0.16, < 2.a)
gpgme (2.0.20)
mini_portile2 (~> 2.3)
grape (1.5.2)
@ -937,7 +969,9 @@ GEM
plist (~> 3.1)
train-core
wmi-lite (~> 1.0)
oj (3.13.21)
oj (3.13.23)
oj-introspect (0.7.0)
oj (>= 3.13.23)
omniauth (2.1.0)
hashie (>= 3.4.6)
rack (>= 2.2.3)
@ -1616,7 +1650,7 @@ DEPENDENCIES
fog-aliyun (~> 0.3)
fog-aws (~> 3.15)
fog-core (= 2.1.0)
fog-google (~> 1.15)
fog-google (~> 1.19)
fog-local (~> 0.8)
fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1)
@ -1705,6 +1739,7 @@ DEPENDENCIES
octokit (~> 4.15)
ohai (~> 16.10)
oj (~> 3.13.21)
oj-introspect (~> 0.7)
omniauth (~> 2.1.0)
omniauth-alicloud (~> 2.0.0)
omniauth-atlassian-oauth2 (~> 0.2.0)

View File

@ -129,9 +129,6 @@ export default {
issuableId() {
return this.issuable?.id;
},
isRealtimeEnabled() {
return this.glFeatures.realtimeLabels;
},
},
apollo: {
issuable: {
@ -163,7 +160,7 @@ export default {
};
},
skip() {
return !this.issuableId || !this.isDropdownVariantSidebar || !this.isRealtimeEnabled;
return !this.issuableId || !this.isDropdownVariantSidebar;
},
updateQuery(
_,

View File

@ -3,7 +3,7 @@ import { GlLink, GlIcon, GlLabel, GlFormCheckbox, GlSprintf, GlTooltipDirective
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isScopedLabel } from '~/lib/utils/common_utils';
import { differenceInSeconds, getTimeago, SECONDS_IN_DAY } from '~/lib/utils/datetime_utility';
import { getTimeago } from '~/lib/utils/datetime_utility';
import { isExternal, setUrlFragment } from '~/lib/utils/url_utility';
import { __, n__, sprintf } from '~/locale';
import IssuableAssignees from '~/issuable/components/issue_assignees.vue';
@ -65,10 +65,6 @@ export default {
issuableIid() {
return this.issuable.iid;
},
createdInPastDay() {
const createdSecondsAgo = differenceInSeconds(new Date(this.issuable.createdAt), new Date());
return createdSecondsAgo < SECONDS_IN_DAY;
},
author() {
return this.issuable.author || {};
},
@ -187,7 +183,7 @@ export default {
<li
:id="`issuable_${issuableId}`"
class="issue gl-display-flex! gl-px-5!"
:class="{ closed: issuable.closedAt, today: createdInPastDay }"
:class="{ closed: issuable.closedAt }"
:data-labels="labelIdsString"
:data-qa-issue-id="issuableId"
>

View File

@ -7,7 +7,9 @@ import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_m
import { __, s__ } from '~/locale';
import EditedAt from '~/issues/show/components/edited.vue';
import Tracking from '~/tracking';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import { getWorkItemQuery } from '../utils';
import workItemDescriptionSubscription from '../graphql/work_item_description.subscription.graphql';
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
@ -19,10 +21,11 @@ export default {
EditedAt,
GlButton,
GlFormGroup,
MarkdownEditor,
MarkdownField,
WorkItemDescriptionRendered,
},
mixins: [Tracking.mixin()],
mixins: [glFeatureFlagMixin(), Tracking.mixin()],
props: {
workItemId: {
type: String,
@ -133,7 +136,7 @@ export default {
await this.$nextTick();
this.$refs.textarea.focus();
this.$refs.textarea?.focus();
},
async cancelEditing() {
const isDirty = this.descriptionText !== this.workItemDescription?.description;
@ -200,6 +203,10 @@ export default {
this.isSubmitting = false;
},
setDescriptionText(newText) {
this.descriptionText = newText;
updateDraft(this.autosaveKey, this.descriptionText);
},
handleDescriptionTextUpdated(newText) {
this.descriptionText = newText;
this.updateWorkItem();
@ -216,7 +223,24 @@ export default {
:label="__('Description')"
label-for="work-item-description"
>
<markdown-editor
v-if="glFeatures.workItemsMvc2"
class="gl-my-3 common-note-form"
:value="descriptionText"
:render-markdown-path="markdownPreviewPath"
:markdown-docs-path="$options.markdownDocsPath"
:form-field-aria-label="__('Description')"
:form-field-placeholder="__('Write a comment or drag your files here…')"
form-field-id="work-item-description"
form-field-name="work-item-description"
enable-autocomplete
init-on-autofocus
@input="setDescriptionText"
@keydown.meta.enter="updateWorkItem"
@keydown.ctrl.enter="updateWorkItem"
/>
<markdown-field
v-else
can-attach-file
:textarea-value="descriptionText"
:is-submitting="isSubmitting"

View File

@ -758,12 +758,6 @@ $input-lg-width: 320px;
$document-index-color: #888;
$help-shortcut-header-color: #333;
/*
* Issues
*/
$issues-today-bg: #f3fff2 !default;
$issues-today-border: #e1e8d5 !default;
/*
* Label
*/

View File

@ -75,11 +75,6 @@ ul.related-merge-requests > li gl-emoji {
.merge-request,
.issue {
&.today {
background: $issues-today-bg;
border-color: $issues-today-border;
}
&.closed,
&.merged {
background: $gray-light;

View File

@ -255,9 +255,6 @@ $popover-arrow-outer-color: $gray-800;
$secondary: $gray-600;
$issues-today-bg: #333838;
$issues-today-border: #333a40;
$yiq-text-dark: $gray-50;
$yiq-text-light: $gray-950;

View File

@ -8,7 +8,6 @@ class Groups::BoardsController < Groups::ApplicationController
before_action do
push_frontend_feature_flag(:board_multi_select, group)
push_frontend_feature_flag(:apollo_boards, group)
push_frontend_feature_flag(:realtime_labels, group)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.control {}
e.candidate {}

View File

@ -8,7 +8,6 @@ class Projects::BoardsController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:board_multi_select, project)
push_frontend_feature_flag(:apollo_boards, project)
push_frontend_feature_flag(:realtime_labels, project&.group)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.control {}
e.candidate {}

View File

@ -51,7 +51,6 @@ class Projects::IssuesController < Projects::ApplicationController
before_action only: :show do
push_frontend_feature_flag(:issue_assignees_widget, project)
push_frontend_feature_flag(:realtime_labels, project)
push_frontend_feature_flag(:work_items_mvc, project&.group)
push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:epic_widget_edit_confirmation, project)

View File

@ -35,7 +35,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:merge_request_widget_graphql, project)
push_frontend_feature_flag(:core_security_mr_widget_counts, project)
push_frontend_feature_flag(:issue_assignees_widget, @project)
push_frontend_feature_flag(:realtime_labels, project)
push_frontend_feature_flag(:refactor_security_extension, @project)
push_frontend_feature_flag(:refactor_code_quality_inline_findings, project)
push_frontend_feature_flag(:moved_mr_sidebar, project)

View File

@ -9,7 +9,11 @@ class SearchController < ApplicationController
RESCUE_FROM_TIMEOUT_ACTIONS = [:count, :show, :autocomplete, :aggregations].freeze
track_event :show, name: 'i_search_total', destinations: [:redis_hll, :snowplow]
track_custom_event :show,
name: 'i_search_total',
label: 'redis_hll_counters.search.search_total_unique_counts_monthly',
action: 'executed',
destinations: [:redis_hll, :snowplow]
def self.search_rate_limited_endpoints
%i[show count autocomplete]
@ -243,6 +247,10 @@ class SearchController < ApplicationController
search_service.project&.namespace || search_service.group
end
def tracking_project_source
search_service.project
end
def search_type
'basic'
end

View File

@ -1,15 +1,22 @@
# frozen_string_literal: true
# Normally this wouldn't be needed and we could use
#
# type Types::IssueType.connection_type, null: true
# in a resolver. However we can end up with cyclic definitions,
# which can result in errors like
#
# in a resolver. However we can end up with cyclic definitions.
# Running the spec locally can result in errors like
#
# NameError: uninitialized constant Resolvers::GroupIssuesResolver
#
# Now we would use
# or other errors. To fix this, we created this file and use
#
# type "Types::IssueConnection", null: true
#
# which gives a delayed resolution, and the proper connection type.
#
# See app/graphql/resolvers/base_issues_resolver.rb
# Reference: https://github.com/rmosolgo/graphql-ruby/issues/3974#issuecomment-1084444214
# and https://docs.gitlab.com/ee/development/api_graphql_styleguide.html#testing-tips-and-tricks
#
Types::IssueConnection = Types::IssueType.connection_type

View File

@ -10,7 +10,6 @@ module IssuesHelper
def issue_css_classes(issue)
classes = ["issue"]
classes << "closed" if issue.closed?
classes << "today" if issue.new?
classes << "gl-cursor-grab" if @sort == 'relative_position'
classes.join(' ')
end

View File

@ -464,18 +464,6 @@ module Issuable
end
end
def today?
Date.today == created_at.to_date
end
def created_hours_ago
(Time.now.utc.to_i - created_at.utc.to_i) / 3600
end
def new?
created_hours_ago < 24
end
def open?
opened?
end

View File

@ -56,7 +56,7 @@ class WebHookLog < ApplicationRecord
def redact_user_emails
self.request_data.deep_transform_values! do |value|
value =~ URI::MailTo::EMAIL_REGEXP ? _('[REDACTED]') : value
value.to_s =~ URI::MailTo::EMAIL_REGEXP ? _('[REDACTED]') : value
end
end
end

View File

@ -1,8 +0,0 @@
---
name: realtime_labels
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/83743
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357370
milestone: '14.10'
type: development
group: group::project management
default_enabled: true

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true
require 'google/apis/core/http_command'
require 'google/apis/version'
raise 'This patch is only tested with google-api-client-ruby v0.50.0' unless Google::Apis::VERSION == "0.50.0"
raise 'This patch is only tested with google-api-client-ruby v0.53.0' unless Google::Apis::VERSION == "0.53.0"
# The google-api-ruby-client does not have a way to increase or disable
# the maximum allowed time for a request to be retried. By default, it

View File

@ -0,0 +1,20 @@
- name: "Changing merge request approvals with the `/approvals` API endpoint"
announcement_milestone: "12.3"
announcement_date: "2019-09-22"
removal_milestone: "16.0"
removal_date: "2023-03-22"
breaking_change: true
reporter: tlinz
stage: Create
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353097
body: | # (required) Do not modify this line, instead modify the lines below.
To change the approvals required for a merge request, you should no longer use the `/approvals` API endpoint, which was deprecated in GitLab 12.3.
Instead, use the [`/approval_rules` endpoint](https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals) to [create](https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule) or [update](https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-merge-request-level-rule) the approval rules for a merge request.
#
# OPTIONAL FIELDS
#
tiers: Premium
documentation_url: https://docs.gitlab.com/ee/api/merge_request_approvals.html
image_url: # (optional) This is a link to a thumbnail image depicting the feature
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg

View File

@ -8581,6 +8581,52 @@ The edge type for [`PipelineSecurityReportFinding`](#pipelinesecurityreportfindi
| <a id="pipelinesecurityreportfindingedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="pipelinesecurityreportfindingedgenode"></a>`node` | [`PipelineSecurityReportFinding`](#pipelinesecurityreportfinding) | The item at the end of the edge. |
#### `ProductAnalyticsDashboardConnection`
The connection type for [`ProductAnalyticsDashboard`](#productanalyticsdashboard).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="productanalyticsdashboardconnectionedges"></a>`edges` | [`[ProductAnalyticsDashboardEdge]`](#productanalyticsdashboardedge) | A list of edges. |
| <a id="productanalyticsdashboardconnectionnodes"></a>`nodes` | [`[ProductAnalyticsDashboard]`](#productanalyticsdashboard) | A list of nodes. |
| <a id="productanalyticsdashboardconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `ProductAnalyticsDashboardEdge`
The edge type for [`ProductAnalyticsDashboard`](#productanalyticsdashboard).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="productanalyticsdashboardedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="productanalyticsdashboardedgenode"></a>`node` | [`ProductAnalyticsDashboard`](#productanalyticsdashboard) | The item at the end of the edge. |
#### `ProductAnalyticsDashboardWidgetConnection`
The connection type for [`ProductAnalyticsDashboardWidget`](#productanalyticsdashboardwidget).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="productanalyticsdashboardwidgetconnectionedges"></a>`edges` | [`[ProductAnalyticsDashboardWidgetEdge]`](#productanalyticsdashboardwidgetedge) | A list of edges. |
| <a id="productanalyticsdashboardwidgetconnectionnodes"></a>`nodes` | [`[ProductAnalyticsDashboardWidget]`](#productanalyticsdashboardwidget) | A list of nodes. |
| <a id="productanalyticsdashboardwidgetconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `ProductAnalyticsDashboardWidgetEdge`
The edge type for [`ProductAnalyticsDashboardWidget`](#productanalyticsdashboardwidget).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="productanalyticsdashboardwidgetedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="productanalyticsdashboardwidgetedgenode"></a>`node` | [`ProductAnalyticsDashboardWidget`](#productanalyticsdashboardwidget) | The item at the end of the edge. |
#### `ProjectConnection`
The connection type for [`Project`](#project).
@ -16483,6 +16529,29 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="previewbillableuserchangenewbillableusercount"></a>`newBillableUserCount` | [`Int`](#int) | Total number of billable users after change. |
| <a id="previewbillableuserchangeseatsinsubscription"></a>`seatsInSubscription` | [`Int`](#int) | Number of seats in subscription. |
### `ProductAnalyticsDashboard`
Represents a product analytics dashboard.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="productanalyticsdashboarddescription"></a>`description` | [`String`](#string) | Description of the dashboard. |
| <a id="productanalyticsdashboardtitle"></a>`title` | [`String!`](#string) | Title of the dashboard. |
| <a id="productanalyticsdashboardwidgets"></a>`widgets` | [`ProductAnalyticsDashboardWidgetConnection!`](#productanalyticsdashboardwidgetconnection) | Widgets shown on the dashboard. (see [Connections](#connections)) |
### `ProductAnalyticsDashboardWidget`
Represents a product analytics dashboard widget.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="productanalyticsdashboardwidgetgridattributes"></a>`gridAttributes` | [`JSON`](#json) | Description of the position and size of the widget. |
| <a id="productanalyticsdashboardwidgettitle"></a>`title` | [`String!`](#string) | Title of the widget. |
### `Project`
#### Fields
@ -17401,6 +17470,26 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="projectpipelinesupdatedbefore"></a>`updatedBefore` | [`Time`](#time) | Pipelines updated before this date. |
| <a id="projectpipelinesusername"></a>`username` | [`String`](#string) | Filter pipelines by the user that triggered the pipeline. |
##### `Project.productAnalyticsDashboards`
Product Analytics dashboards of the project.
WARNING:
**Introduced** in 15.6.
This feature is in Alpha. It can be changed or removed at any time.
Returns [`ProductAnalyticsDashboardConnection`](#productanalyticsdashboardconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectproductanalyticsdashboardsslug"></a>`slug` | [`String`](#string) | Find by dashboard slug. |
##### `Project.projectMembers`
Members of the project.

View File

@ -6,6 +6,8 @@ info: "To determine the technical writer assigned to the Stage/Group associated
# Merge request approvals API **(PREMIUM)**
> Changing approval configuration with the `/approvals` endpoint was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3.
Configuration for
[approvals on all merge requests](../user/project/merge_requests/approvals/index.md)
in the project. Must be authenticated for all endpoints.
@ -57,7 +59,7 @@ Supported attributes:
| Attribute | Type | Required | Description |
| ------------------------------------------------ | ------- | -------- | -- |
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_before_merge` | integer | **{dotted-circle}** No | How many approvals are required before a merge request can be merged. Deprecated in GitLab 12.0 in favor of Approval Rules API. |
| `approvals_before_merge` (deprecated) | integer | **{dotted-circle}** No | How many approvals are required before a merge request can be merged. [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3. |
| `disable_overriding_approvers_per_merge_request` | boolean | **{dotted-circle}** No | Allow or prevent overriding approvers per merge request. |
| `merge_requests_author_approval` | boolean | **{dotted-circle}** No | Allow or prevent authors from self approving merge requests; `true` means authors can self approve. |
| `merge_requests_disable_committers_approval` | boolean | **{dotted-circle}** No | Allow or prevent committers from self approving merge requests. |
@ -582,9 +584,16 @@ Supported attributes:
}
```
### Change approval configuration
### Change approval configuration (deprecated)
> Moved to GitLab Premium in 13.9.
> - Moved to GitLab Premium in GitLab 13.9.
> - Endpoint `/approvals` [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3.
WARNING:
The `/approvals` endpoint was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/11132) in GitLab 12.3
and is planned for removal in 16.0. To change the approvals required for a merge request,
use the `/approval_rules` endpoint described in [Create merge request level rule](#create-merge-request-level-rule).
on this page. This change is a breaking change.
If you are allowed to, you can change `approvals_required` using the following
endpoint:
@ -598,7 +607,7 @@ Supported attributes:
| Attribute | Type | Required | Description |
|----------------------|-------------------|----------|-------------|
| `id` | integer or string | **{check-circle}** Yes | The ID or [URL-encoded path of a project](index.md#namespaced-path-encoding). |
| `approvals_required` | integer | **{check-circle}** Yes | Approvals required before MR can be merged. Deprecated in GitLab 12.0 in favor of Approval Rules API. |
| `approvals_required` | integer | **{check-circle}** Yes | Approvals required before MR can be merged. |
| `merge_request_iid` | integer | **{check-circle}** Yes | The IID of the merge request. |
```json

View File

@ -121,8 +121,8 @@ defined external service. This includes confidential merge requests.
| Attribute | Type | Required | Description |
|------------------------|------------------|----------|------------------------------------------------|
| `id` | integer | yes | ID of a project |
| `name` | string | yes | Display name of status check |
| `external_url` | string | yes | URL of status check resource |
| `name` | string | yes | Display name of external status check |
| `external_url` | string | yes | URL of external status check resource |
| `protected_branch_ids` | `array<Integer>` | no | IDs of protected branches to scope the rule by |
## Delete external status check
@ -135,7 +135,7 @@ DELETE /projects/:id/external_status_checks/:check_id
| Attribute | Type | Required | Description |
|------------------------|----------------|----------|-----------------------|
| `rule_id` | integer | yes | ID of an status check |
| `check_id` | integer | yes | ID of an external status check |
| `id` | integer | yes | ID of a project |
## Update external status check
@ -149,8 +149,8 @@ PUT /projects/:id/external_status_checks/:check_id
| Attribute | Type | Required | Description |
|------------------------|------------------|----------|------------------------------------------------|
| `id` | integer | yes | ID of a project |
| `rule_id` | integer | yes | ID of an external status check |
| `name` | string | no | Display name of status check |
| `check_id` | integer | yes | ID of an external status check |
| `name` | string | no | Display name of external status check |
| `external_url` | string | no | URL of external status check resource |
| `protected_branch_ids` | `array<Integer>` | no | IDs of protected branches to scope the rule by |

View File

@ -2178,32 +2178,44 @@ end
```ruby
NameError: uninitialized constant Resolvers::GroupIssuesResolver
or
GraphQL::Pagination::Connections::ImplementationMissingError
```
though you might see something different.
To fix this, we must create a new file that encapsulates the connection type,
and then reference it using double quotes. This gives a delayed resolution,
and the proper connection type. For example:
```ruby
module Types
# rubocop: disable Graphql/AuthorizeTypes
class IssueConnectionType < CountableConnectionType
end
end
[app/graphql/resolvers/base_issues_resolver.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/resolvers/base_issues_resolver.rb)
originally contained the line
Types::IssueConnectionType.prepend_mod_with('Types::IssueConnectionType')
```ruby
type Types::IssueType.connection_type, null: true
```
in [types/issue_connection_type.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/issue_connection_type.rb)
defines a new `Types::IssueConnectionType`, and is then referenced in
[app/graphql/resolvers/base_issues_resolver.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/resolvers/base_issues_resolver.rb)
Running the specs locally for this file caused the
`NameError: uninitialized constant Resolvers::GroupIssuesResolver` error.
The fix was to create a new file, [app/graphql/types/issue_connection.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/issue_connection.rb) with the
line:
```ruby
Types::IssueConnection = Types::IssueType.connection_type
```
and in [app/graphql/resolvers/base_issues_resolver.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/resolvers/base_issues_resolver.rb)
we use the line
```ruby
type "Types::IssueConnection", null: true
```
Only use this style if you are having spec failures. This is not intended to be a new
pattern that we use. This issue may disappear after we've upgraded to `2.x`.
pattern that we use. This issue should disappear after we've upgraded to `2.x`.
- There can be instances where a spec fails because the class is not loaded correctly.
It relates to the

View File

@ -496,8 +496,6 @@ metric counters.
| Attribute | Type | Required | Description |
|:---------------------------------------------------------------------------|:--------------|:---------|:-----------------------------------------------------------------------------------------------------------------|
| `gitops_sync_count` (DEPRECATED) | integer | no | The number to increase the `gitops_sync` counter by |
| `k8s_api_proxy_request_count` (DEPRECATED) | integer | no | The number to increase the `k8s_api_proxy_request` counter by |
| `counters` | hash | no | The number to increase the `k8s_api_proxy_request` counter by |
| `counters["k8s_api_proxy_request"]` | integer | no | The number to increase the `k8s_api_proxy_request` counter by |
| `counters["gitops_sync"]` | integer | no | The number to increase the `gitops_sync` counter by |
@ -512,7 +510,7 @@ Example Request:
```shell
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Content-Type: application/json" \
--data '{"gitops_sync_count":1}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
--data '{"counters": {"gitops_sync":1}}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
```
### Create Starboard vulnerability

View File

@ -25,6 +25,10 @@ Any such changes lead to inconsistent reports from multiple GitLab instances.
If there is a problem with an existing metric, it's best to deprecate the existing metric,
and use it, side by side, with the desired new metric.
If you do need to change a metric, please notify the Customer Success Ops team (`@csops-team`), Analytics Engineers (`@gitlab-data/analytics-engineers`), and Product Analysts (`@gitlab-data/product-analysts`) teams by `@` mentioning those groups in a comment on the MR.
Many Service Ping metrics are relied upon for health score and XMAU reporting and
unexpected changes to those metrics could break reporting.
Example:
Consider following change. Before GitLab 12.6, the `example_metric` was implemented as:
@ -135,3 +139,6 @@ To remove a metric:
1. Remove any other records related to the metric:
- The feature flag YAML file at [`config/feature_flags/*/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/config/feature_flags).
- The entry in the known events YAML file at [`lib/gitlab/usage_data_counters/known_events/*.yaml`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/usage_data_counters/known_events).
1. Notify the Customer Success Ops team (`@csops-team`), Analytics Engineers (`@gitlab-data/analytics-engineers`), and Product Analysts (`@gitlab-data/product-analysts`) by `@` mentioning those groups in a comment on the MR.
Many Service Ping metrics are relied upon for health score and XMAU reporting and unexpected changes to those metrics could break reporting.

View File

@ -68,6 +68,7 @@ are regular backend changes.
Read the [stages file](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml).
- Check the file location. Consider the time frame, and if the file should be under `ee`.
- Check the tiers.
- If a metric was changed or removed: Make sure the MR author notified the Customer Success Ops team (`@csops-team`), Analytics Engineers (`@gitlab-data/analytics-engineers`), and Product Analysts (`@gitlab-data/product-analysts`) by `@` mentioning those groups in a comment on the MR.
- Metrics instrumentations
- Recommend using metrics instrumentation for new metrics, [if possible](metrics_instrumentation.md#support-for-instrumentation-classes).
- Approve the MR, and relabel the MR with `~"product intelligence::approved"`.

View File

@ -31,6 +31,18 @@ For removal reviewers (Technical Writers only):
https://about.gitlab.com/handbook/marketing/blog/release-posts/#update-the-removals-doc
-->
## Removed in 16.0
### Changing merge request approvals with the `/approvals` API endpoint
WARNING:
This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
Review the details carefully before upgrading.
To change the approvals required for a merge request, you should no longer use the `/approvals` API endpoint, which was deprecated in GitLab 12.3.
Instead, use the [`/approval_rules` endpoint](https://docs.gitlab.com/ee/api/merge_request_approvals.html#merge-request-level-mr-approvals) to [create](https://docs.gitlab.com/ee/api/merge_request_approvals.html#create-merge-request-level-rule) or [update](https://docs.gitlab.com/ee/api/merge_request_approvals.html#update-merge-request-level-rule) the approval rules for a merge request.
## Removed in 15.4
### SAST analyzer consolidation and CI/CD template changes

View File

@ -4,7 +4,7 @@ group: Static Analysis
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Infrastructure as Code (IaC) Scanning
# Infrastructure as Code (IaC) Scanning **(FREE)**
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6655) in GitLab 14.5.

View File

@ -46,5 +46,9 @@ To remove an award emoji, select the emoji again.
You can upload custom emojis to a GitLab instance with the GraphQL API.
For more, visit [Use custom emojis with GraphQL](../api/graphql/custom_emoji.md).
Custom emojis don't show in the emoji picker.
To use them in a text box, type the filename without the extension and surrounded by colons.
For example, for a file named `thank-you.png`, type `:thank-you:`.
For the list of custom emojis available for GitLab.com, visit
[the `custom_emoji` project](https://gitlab.com/custom_emoji/custom_emoji/-/tree/main/img).

View File

@ -673,7 +673,7 @@ you can see the change without having to refresh the page.
The following sections are updated in real time:
- [Assignee](#assignee)
- Labels, [if enabled](../labels.md#real-time-changes-to-labels)
- [Labels](../labels.md#assign-and-unassign-labels)
## Assignee

View File

@ -28,10 +28,21 @@ You can use two types of labels in GitLab:
## Assign and unassign labels
> Unassigning labels with the **X** button [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216881) in GitLab 13.5.
> - Unassigning labels with the **X** button [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216881) in GitLab 13.5.
> - Real-time updates in the sidebar [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241538) in GitLab 14.10 with a [feature flag](../../administration/feature_flags.md) named `realtime_labels`, disabled by default.
> - Real-time updates in the sidebar [enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/357370#note_991987201) in GitLab 15.1.
> - Real-time updates in the sidebar [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/357370) in GitLab 15.5.
> - Real-time updates in the sidebar [generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103199) in GitLab 15.6. Feature flag `realtime_labels` removed.
You can assign labels to any issue, merge request, or epic.
Changed labels are immediately visible to other users, without refreshing the page, on the following:
- Epics
- Incidents
- Issues
- Merge requests
To assign or unassign a label:
1. In the **Labels** section of the sidebar, select **Edit**.
@ -444,23 +455,6 @@ The labels higher in the list get higher priority.
To learn what happens when you sort by priority or label priority, see
[Sorting and ordering issue lists](issues/sorting_issue_lists.md).
## Real-time changes to labels
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241538) in GitLab 14.10 with a [feature flag](../../administration/feature_flags.md) named `realtime_labels`, disabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/357370#note_991987201) in GitLab 15.1.
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/357370) in GitLab 15.5.
FLAG:
On self-managed GitLab, to prevent updating labels in real-time, you can ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `realtime_labels`.
On GitLab.com, this feature is available.
Changed labels are immediately visible to other users, without refreshing the page, on the following:
- Epics
- Incidents
- Issues
- Merge requests
## Troubleshooting
### Some label titles end with `_duplicate<number>`

View File

@ -195,6 +195,7 @@ module API
mount ::API::FreezePeriods
mount ::API::GroupClusters
mount ::API::GroupExport
mount ::API::GroupVariables
mount ::API::ImportBitbucketServer
mount ::API::ImportGithub
mount ::API::Keys
@ -272,7 +273,6 @@ module API
mount ::API::GroupLabels
mount ::API::GroupMilestones
mount ::API::GroupPackages
mount ::API::GroupVariables
mount ::API::Groups
mount ::API::HelmPackages
mount ::API::Integrations

View File

@ -11,12 +11,14 @@ module API
helpers ::API::Helpers::VariablesHelpers
params do
requires :id, type: String, desc: 'The ID of a group'
requires :id, type: String, desc: 'The ID of a group or URL-encoded path of the group owned by the authenticated
user'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get group-level variables' do
desc 'Get a list of group-level variables' do
success Entities::Ci::Variable
tags %w[ci_variables]
end
params do
use :pagination
@ -26,8 +28,10 @@ module API
present paginate(variables), with: Entities::Ci::Variable
end
desc 'Get a specific variable from a group' do
desc 'Get the details of a groups specific variable' do
success Entities::Ci::Variable
failure [{ code: 404, message: 'Group Variable Not Found' }]
tags %w[ci_variables]
end
params do
requires :key, type: String, desc: 'The key of the variable'
@ -42,16 +46,19 @@ module API
desc 'Create a new variable in a group' do
success Entities::Ci::Variable
failure [{ code: 400, message: '400 Bad Request' }]
tags %w[ci_variables]
end
route_setting :log_safety, { safe: %w[key], unsafe: %w[value] }
params do
requires :key, type: String, desc: 'The key of the variable'
requires :value, type: String, desc: 'The value of the variable'
requires :key, type: String, desc: 'The ID of a group or URL-encoded path of the group owned by the
authenticated user'
requires :value, type: String, desc: 'The value of a variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
optional :masked, type: String, desc: 'Whether the variable is masked'
optional :raw, type: String, desc: 'Whether the variable will be expanded'
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var'
optional :environment_scope, type: String, desc: 'The environment scope of a variable'
use :optional_group_variable_params_ee
end
post ':id/variables' do
@ -75,15 +82,18 @@ module API
desc 'Update an existing variable from a group' do
success Entities::Ci::Variable
failure [{ code: 400, message: '400 Bad Request' }, { code: 404, message: 'Group Variable Not Found' }]
tags %w[ci_variables]
end
route_setting :log_safety, { safe: %w[key], unsafe: %w[value] }
params do
optional :key, type: String, desc: 'The key of the variable'
optional :value, type: String, desc: 'The value of the variable'
optional :key, type: String, desc: 'The key of a variable'
optional :value, type: String, desc: 'The value of a variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
optional :masked, type: String, desc: 'Whether the variable is masked'
optional :raw, type: String, desc: 'Whether the variable will be expanded'
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of the variable. Default: env_var'
optional :environment_scope, type: String, desc: 'The environment scope of a variable'
use :optional_group_variable_params_ee
end
@ -110,9 +120,11 @@ module API
desc 'Delete an existing variable from a group' do
success Entities::Ci::Variable
failure [{ code: 404, message: 'Group Variable Not Found' }]
tags %w[ci_variables]
end
params do
requires :key, type: String, desc: 'The key of the variable'
requires :key, type: String, desc: 'The key of a variable'
end
delete ':id/variables/:key' do
variable = find_variable(user_group, params)

View File

@ -61,15 +61,6 @@ module API
Guest.can?(:download_code, project) || agent.has_access_to?(project)
end
def count_events
strong_memoize(:count_events) do
events = params.slice(:gitops_sync_count, :k8s_api_proxy_request_count)
events.transform_keys! { |event| event.to_s.chomp('_count') }
events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request) unless events.present?
events
end
end
def increment_unique_events
events = params[:unique_counters]&.slice(:agent_users_using_ci_tunnel)
@ -77,6 +68,12 @@ module API
increment_unique_values(event, entity_ids)
end
end
def increment_count_events
events = params[:counters]&.slice(:gitops_sync, :k8s_api_proxy_request)
Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(events)
end
end
namespace 'internal' do
@ -144,26 +141,17 @@ module API
detail 'Updates usage metrics for agent'
end
params do
# Todo: Remove gitops_sync_count and k8s_api_proxy_request_count in the next milestone
# https://gitlab.com/gitlab-org/gitlab/-/issues/369489
# We're only keeping it for backwards compatibility until KAS is released
# using `counts:` instead
optional :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by'
optional :k8s_api_proxy_request_count, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by'
optional :counters, type: Hash do
optional :gitops_sync, type: Integer, desc: 'The count to increment the gitops_sync metric by'
optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by'
optional :k8s_api_proxy_request, type: Integer, desc: 'The count to increment the k8s_api_proxy_request metric by'
end
mutually_exclusive :counters, :gitops_sync_count
mutually_exclusive :counters, :k8s_api_proxy_request_count
optional :unique_counters, type: Hash do
optional :agent_users_using_ci_tunnel, type: Set[Integer], desc: 'A set of user ids that have interacted a CI Tunnel to'
end
end
post '/' do
Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(count_events) if count_events
increment_count_events
increment_unique_events
no_content!

View File

@ -41,7 +41,7 @@ module Gitlab
private
attr_reader :json_data, :report, :validate
attr_reader :json_data, :report, :validate, :project
def valid?
return true unless validate

View File

@ -156,6 +156,14 @@ module Gitlab
signatures.present?
end
def false_positive?
flags.any?(&:false_positive?)
end
def remediation_byte_offsets
remediations.map(&:byte_offsets).compact
end
def raw_metadata
@raw_metadata ||= original_data.to_json
end
@ -176,6 +184,10 @@ module Gitlab
original_data['location']
end
def assets
original_data['assets'] || []
end
# Returns either the max priority signature hex
# or the location fingerprint
def location_fingerprint

View File

@ -27,6 +27,10 @@ module Gitlab
description: description
}.compact
end
def false_positive?
flag_type == :false_positive
end
end
end
end

View File

@ -171,16 +171,6 @@ module Gitlab
end
end
def strong_memoize_with(name, *args)
container = strong_memoize(name) { {} }
if container.key?(args)
container[args]
else
container[args] = yield
end
end
def release
return unless @pipeline.tag?

View File

@ -9,19 +9,23 @@ module Gitlab
'<a href="https://docs.gitlab.com/ee/install/requirements.html#database">database requirements</a>'
def check
unsupported_database = Gitlab::Database
unsupported_databases = Gitlab::Database
.database_base_models
.map { |_, model| Gitlab::Database::Reflection.new(model) }
.reject(&:postgresql_minimum_supported_version?)
.each_with_object({}) do |(database_name, base_model), databases|
database = Gitlab::Database::Reflection.new(base_model)
unsupported_database.map do |database|
databases[database_name] = database unless database.postgresql_minimum_supported_version?
end
unsupported_databases.map do |database_name, database|
{
type: 'warning',
message: _('You are using PostgreSQL %{pg_version_current}, but PostgreSQL ' \
'%{pg_version_minimum} is required for this version of GitLab. ' \
message: _('Database \'%{database_name}\' is using PostgreSQL %{pg_version_current}, ' \
'but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. ' \
'Please upgrade your environment to a supported PostgreSQL version, ' \
'see %{pg_requirements_url} for details.') % \
{
database_name: database_name,
pg_version_current: database.version,
pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION,
pg_requirements_url: PG_REQUIREMENTS_LINK

View File

@ -8,6 +8,8 @@ module Gitlab
class << self
def increment_event_counts(events)
return unless events.present?
validate!(events)
events.each do |event, incr|

View File

@ -45,6 +45,16 @@ module Gitlab
end
end
def strong_memoize_with(name, *args)
container = strong_memoize(name) { {} }
if container.key?(args)
container[args]
else
container[args] = yield
end
end
def strong_memoized?(name)
instance_variable_defined?(ivar(name))
end

View File

@ -12597,6 +12597,9 @@ msgstr ""
msgid "Data type"
msgstr ""
msgid "Database '%{database_name}' is using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url} for details."
msgstr ""
msgid "Database update failed"
msgstr ""
@ -32257,6 +32260,9 @@ msgstr ""
msgid "ProjectSettings|Users can only push commits to this repository if the committer email is one of their own verified emails."
msgstr ""
msgid "ProjectSettings|Users can only push commits to this repository if the committer name is consistent with their git config username."
msgstr ""
msgid "ProjectSettings|Users can request access"
msgstr ""
@ -33295,6 +33301,9 @@ msgstr ""
msgid "PushRule|Push rules"
msgstr ""
msgid "PushRule|Reject inconsistent user name"
msgstr ""
msgid "PushRule|Reject unverified users"
msgstr ""
@ -46436,9 +46445,6 @@ msgstr ""
msgid "You are trying to upload something other than an image. Please upload a .png, .jpg, .jpeg, .gif, .bmp, .tiff or .ico."
msgstr ""
msgid "You are using PostgreSQL %{pg_version_current}, but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. Please upgrade your environment to a supported PostgreSQL version, see %{pg_requirements_url} for details."
msgstr ""
msgid "You can %{gitlabLinkStart}resolve conflicts on GitLab%{gitlabLinkEnd} or %{resolveLocallyStart}resolve it locally%{resolveLocallyEnd}."
msgstr ""

View File

@ -223,7 +223,14 @@ RSpec.describe SearchController do
let(:project) { nil }
let(:category) { described_class.to_s }
let(:action) { 'i_search_total' }
let(:action) { 'executed' }
let(:label) { 'redis_hll_counters.search.search_total_unique_counts_monthly' }
let(:property) { 'i_search_total' }
let(:context) do
[Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll,
event: property).to_context]
end
let(:namespace) { create(:group) }
let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
end

View File

@ -3,12 +3,11 @@
require 'spec_helper'
RSpec.describe 'Admin::HookLogs' do
let(:project) { create(:project) }
let(:system_hook) { create(:system_hook) }
let(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
let_it_be(:system_hook) { create(:system_hook) }
let_it_be(:hook_log) { create(:web_hook_log, web_hook: system_hook, internal_error_message: 'some error') }
let_it_be(:admin) { create(:admin) }
before do
admin = create(:admin)
sign_in(admin)
gitlab_enable_admin_mode_sign_in(admin)
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe 'Admin::Hooks' do
include Spec::Support::Helpers::ModalHelpers
let(:user) { create(:admin) }
let_it_be(:user) { create(:admin) }
before do
sign_in(user)

View File

@ -49,7 +49,6 @@ describe('LabelsSelectRoot', () => {
issuableType = IssuableType.Issue,
queryHandler = successfulQueryHandler,
mutationHandler = successfulMutationHandler,
isRealtimeEnabled = false,
} = {}) => {
const mockApollo = createMockApollo([
[issueLabelsQuery, queryHandler],
@ -74,9 +73,6 @@ describe('LabelsSelectRoot', () => {
allowLabelEdit: true,
allowLabelCreate: true,
labelsManagePath: 'test',
glFeatures: {
realtimeLabels: isRealtimeEnabled,
},
},
});
};
@ -204,17 +200,10 @@ describe('LabelsSelectRoot', () => {
});
});
it('does not emit `updateSelectedLabels` event when the subscription is triggered and FF is disabled', async () => {
it('emits `updateSelectedLabels` event when the subscription is triggered', async () => {
createComponent();
await waitForPromises();
expect(wrapper.emitted('updateSelectedLabels')).toBeUndefined();
});
it('emits `updateSelectedLabels` event when the subscription is triggered and FF is enabled', async () => {
createComponent({ isRealtimeEnabled: true });
await waitForPromises();
expect(wrapper.emitted('updateSelectedLabels')).toEqual([
[
{

View File

@ -543,24 +543,6 @@ describe('IssuableItem', () => {
});
});
describe('when issuable was created within the past 24 hours', () => {
it('renders issuable card with a recently-created style', () => {
wrapper = createComponent({
issuable: { ...mockIssuable, createdAt: '2020-12-10T12:34:56' },
});
expect(wrapper.classes()).toContain('today');
});
});
describe('when issuable was created earlier than the past 24 hours', () => {
it('renders issuable card without a recently-created style', () => {
wrapper = createComponent({ issuable: { ...mockIssuable, createdAt: '2020-12-09' } });
expect(wrapper.classes()).not.toContain('today');
});
});
describe('scoped labels', () => {
describe.each`
description | labelPosition | hasScopedLabelsFeature | scoped

View File

@ -8,6 +8,7 @@ import EditedAt from '~/issues/show/components/edited.vue';
import { updateDraft } from '~/lib/utils/autosave';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
import WorkItemDescription from '~/work_items/components/work_item_description.vue';
import WorkItemDescriptionRendered from '~/work_items/components/work_item_description_rendered.vue';
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
@ -37,12 +38,19 @@ describe('WorkItemDescription', () => {
const subscriptionHandler = jest.fn().mockResolvedValue(workItemDescriptionSubscriptionResponse);
const workItemByIidResponseHandler = jest.fn().mockResolvedValue(projectWorkItemResponse);
let workItemResponseHandler;
let workItemsMvc2;
const findMarkdownField = () => wrapper.findComponent(MarkdownField);
const findMarkdownEditor = () => wrapper.findComponent(MarkdownEditor);
const findRenderedDescription = () => wrapper.findComponent(WorkItemDescriptionRendered);
const findEditedAt = () => wrapper.findComponent(EditedAt);
const editDescription = (newText) => wrapper.find('textarea').setValue(newText);
const editDescription = (newText) => {
if (workItemsMvc2) {
return findMarkdownEditor().vm.$emit('input', newText);
}
return wrapper.find('textarea').setValue(newText);
};
const clickCancel = () => wrapper.find('[data-testid="cancel"]').vm.$emit('click');
const clickSave = () => wrapper.find('[data-testid="save-description"]').vm.$emit('click', {});
@ -72,6 +80,11 @@ describe('WorkItemDescription', () => {
},
fetchByIid,
},
provide: {
glFeatures: {
workItemsMvc2,
},
},
stubs: {
MarkdownField,
},
@ -90,175 +103,178 @@ describe('WorkItemDescription', () => {
wrapper.destroy();
});
it('has a subscription', async () => {
createComponent();
await waitForPromises();
expect(subscriptionHandler).toHaveBeenCalledWith({
issuableId: workItemQueryResponse.data.workItem.id,
});
});
describe('editing description', () => {
it('shows edited by text', async () => {
const lastEditedAt = '2022-09-21T06:18:42Z';
const lastEditedBy = {
name: 'Administrator',
webPath: '/root',
};
await createComponent({
workItemResponse: workItemResponseFactory({
lastEditedAt,
lastEditedBy,
}),
describe.each([true, false])(
'editing description with workItemsMvc2 %workItemsMvc2Enabled',
(workItemsMvc2Enabled) => {
beforeEach(() => {
beforeEach(() => {
workItemsMvc2 = workItemsMvc2Enabled;
});
});
expect(findEditedAt().props()).toEqual({
updatedAt: lastEditedAt,
updatedByName: lastEditedBy.name,
updatedByPath: lastEditedBy.webPath,
});
});
describe('editing description', () => {
it('shows edited by text', async () => {
const lastEditedAt = '2022-09-21T06:18:42Z';
const lastEditedBy = {
name: 'Administrator',
webPath: '/root',
};
it('does not show edited by text', async () => {
await createComponent();
await createComponent({
workItemResponse: workItemResponseFactory({
lastEditedAt,
lastEditedBy,
}),
});
expect(findEditedAt().exists()).toBe(false);
});
expect(findEditedAt().props()).toEqual({
updatedAt: lastEditedAt,
updatedByName: lastEditedBy.name,
updatedByPath: lastEditedBy.webPath,
});
});
it('cancels when clicking cancel', async () => {
await createComponent({
isEditing: true,
});
it('does not show edited by text', async () => {
await createComponent();
clickCancel();
expect(findEditedAt().exists()).toBe(false);
});
await nextTick();
it('cancels when clicking cancel', async () => {
await createComponent({
isEditing: true,
});
expect(confirmAction).not.toHaveBeenCalled();
expect(findMarkdownField().exists()).toBe(false);
});
clickCancel();
it('prompts for confirmation when clicking cancel after changes', async () => {
await createComponent({
isEditing: true,
});
await nextTick();
editDescription('updated desc');
expect(confirmAction).not.toHaveBeenCalled();
expect(findMarkdownField().exists()).toBe(false);
});
clickCancel();
it('prompts for confirmation when clicking cancel after changes', async () => {
await createComponent({
isEditing: true,
});
await nextTick();
editDescription('updated desc');
expect(confirmAction).toHaveBeenCalled();
});
clickCancel();
it('calls update widgets mutation', async () => {
await createComponent({
isEditing: true,
});
await nextTick();
editDescription('updated desc');
expect(confirmAction).toHaveBeenCalled();
});
clickSave();
it('calls update widgets mutation', async () => {
const updatedDesc = 'updated desc';
await waitForPromises();
await createComponent({
isEditing: true,
});
expect(mutationSuccessHandler).toHaveBeenCalledWith({
input: {
id: workItemId,
descriptionWidget: {
description: 'updated desc',
},
},
});
});
editDescription(updatedDesc);
it('tracks editing description', async () => {
await createComponent({
isEditing: true,
markdownPreviewPath: '/preview',
});
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
clickSave();
clickSave();
await waitForPromises();
await waitForPromises();
expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_description', {
category: TRACKING_CATEGORY_SHOW,
label: 'item_description',
property: 'type_Task',
});
});
it('emits error when mutation returns error', async () => {
const error = 'eror';
await createComponent({
isEditing: true,
mutationHandler: jest.fn().mockResolvedValue({
data: {
workItemUpdate: {
workItem: {},
errors: [error],
expect(mutationSuccessHandler).toHaveBeenCalledWith({
input: {
id: workItemId,
descriptionWidget: {
description: updatedDesc,
},
},
},
}),
});
});
it('tracks editing description', async () => {
await createComponent({
isEditing: true,
markdownPreviewPath: '/preview',
});
const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
clickSave();
await waitForPromises();
expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_description', {
category: TRACKING_CATEGORY_SHOW,
label: 'item_description',
property: 'type_Task',
});
});
it('emits error when mutation returns error', async () => {
const error = 'eror';
await createComponent({
isEditing: true,
mutationHandler: jest.fn().mockResolvedValue({
data: {
workItemUpdate: {
workItem: {},
errors: [error],
},
},
}),
});
editDescription('updated desc');
clickSave();
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[error]]);
});
it('emits error when mutation fails', async () => {
const error = 'eror';
await createComponent({
isEditing: true,
mutationHandler: jest.fn().mockRejectedValue(new Error(error)),
});
editDescription('updated desc');
clickSave();
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[error]]);
});
it('autosaves description', async () => {
await createComponent({
isEditing: true,
});
editDescription('updated desc');
expect(updateDraft).toHaveBeenCalled();
});
});
editDescription('updated desc');
it('calls the global ID work item query when `fetchByIid` prop is false', async () => {
createComponent({ fetchByIid: false });
await waitForPromises();
clickSave();
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[error]]);
});
it('emits error when mutation fails', async () => {
const error = 'eror';
await createComponent({
isEditing: true,
mutationHandler: jest.fn().mockRejectedValue(new Error(error)),
expect(workItemResponseHandler).toHaveBeenCalled();
expect(workItemByIidResponseHandler).not.toHaveBeenCalled();
});
editDescription('updated desc');
it('calls the IID work item query when when `fetchByIid` prop is true', async () => {
createComponent({ fetchByIid: true });
await waitForPromises();
clickSave();
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[error]]);
});
it('autosaves description', async () => {
await createComponent({
isEditing: true,
expect(workItemResponseHandler).not.toHaveBeenCalled();
expect(workItemByIidResponseHandler).toHaveBeenCalled();
});
editDescription('updated desc');
expect(updateDraft).toHaveBeenCalled();
});
});
it('calls the global ID work item query when `fetchByIid` prop is false', async () => {
createComponent({ fetchByIid: false });
await waitForPromises();
expect(workItemResponseHandler).toHaveBeenCalled();
expect(workItemByIidResponseHandler).not.toHaveBeenCalled();
});
it('calls the IID work item query when when `fetchByIid` prop is true', async () => {
createComponent({ fetchByIid: true });
await waitForPromises();
expect(workItemResponseHandler).not.toHaveBeenCalled();
expect(workItemByIidResponseHandler).toHaveBeenCalled();
});
},
);
});

View File

@ -29,5 +29,11 @@ RSpec.describe Gitlab::Ci::Reports::Security::Flag do
)
end
end
describe '#false_positive?' do
subject { security_flag.false_positive? }
it { is_expected.to be_truthy }
end
end
end

View File

@ -36,7 +36,7 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
end
it 'reports deprecated database notice' do
is_expected.to contain_exactly(notice_deprecated_database(old_database_version))
is_expected.to contain_exactly(notice_deprecated_database('main', old_database_version))
end
end
end
@ -59,13 +59,13 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
it 'reports deprecated database notice if the main database is using an old version' do
allow(Gitlab::Database::Reflection).to receive(:new).with(ActiveRecord::Base).and_return(old_database)
allow(Gitlab::Database::Reflection).to receive(:new).with(Ci::ApplicationRecord).and_return(new_database)
is_expected.to contain_exactly(notice_deprecated_database(old_database_version))
is_expected.to contain_exactly(notice_deprecated_database('main', old_database_version))
end
it 'reports deprecated database notice if the ci database is using an old version' do
allow(Gitlab::Database::Reflection).to receive(:new).with(ActiveRecord::Base).and_return(new_database)
allow(Gitlab::Database::Reflection).to receive(:new).with(Ci::ApplicationRecord).and_return(old_database)
is_expected.to contain_exactly(notice_deprecated_database(old_database_version))
is_expected.to contain_exactly(notice_deprecated_database('ci', old_database_version))
end
end
@ -77,22 +77,23 @@ RSpec.describe Gitlab::ConfigChecker::ExternalDatabaseChecker do
it 'reports deprecated database notice' do
is_expected.to match_array [
notice_deprecated_database(old_database_version),
notice_deprecated_database(old_database_version)
notice_deprecated_database('main', old_database_version),
notice_deprecated_database('ci', old_database_version)
]
end
end
end
end
def notice_deprecated_database(database_version)
def notice_deprecated_database(database_name, database_version)
{
type: 'warning',
message: _('You are using PostgreSQL %{pg_version_current}, but PostgreSQL ' \
'%{pg_version_minimum} is required for this version of GitLab. ' \
'Please upgrade your environment to a supported PostgreSQL version, ' \
'see %{pg_requirements_url} for details.') % \
message: _('Database \'%{database_name}\' is using PostgreSQL %{pg_version_current}, ' \
'but PostgreSQL %{pg_version_minimum} is required for this version of GitLab. ' \
'Please upgrade your environment to a supported PostgreSQL version, ' \
'see %{pg_requirements_url} for details.') % \
{
database_name: database_name,
pg_version_current: database_version,
pg_version_minimum: Gitlab::Database::MINIMUM_POSTGRES_VERSION,
pg_requirements_url: Gitlab::ConfigChecker::ExternalDatabaseChecker::PG_REQUIREMENTS_LINK

View File

@ -26,6 +26,12 @@ RSpec.describe Gitlab::UsageDataCounters::KubernetesAgentCounter do
expect(described_class.totals).to eq(kubernetes_agent_gitops_sync: 3, kubernetes_agent_k8s_api_proxy_request: 6)
end
context 'with empty events' do
let(:events) { nil }
it { expect { subject }.not_to change(described_class, :totals) }
end
context 'event is unknown' do
let(:events) do
{

View File

@ -46,6 +46,13 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
true
end
def method_name_with_args(*args)
strong_memoize_with(:method_name_with_args, args) do
trace << [value, args]
value
end
end
def trace
@trace ||= []
end
@ -141,6 +148,36 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
end
end
describe '#strong_memoize_with' do
[nil, false, true, 'value', 0, [0]].each do |value|
context "with value #{value}" do
let(:value) { value }
it 'only calls the block once' do
value0 = object.method_name_with_args(1)
value1 = object.method_name_with_args(1)
value2 = object.method_name_with_args([2, 3])
value3 = object.method_name_with_args([2, 3])
expect(value0).to eq(value)
expect(value1).to eq(value)
expect(value2).to eq(value)
expect(value3).to eq(value)
expect(object.trace).to contain_exactly([value, [1]], [value, [[2, 3]]])
end
it 'returns and defines the instance variable for the exact value' do
returned_value = object.method_name_with_args(1, 2, 3)
memoized_value = object.instance_variable_get(:@method_name_with_args)
expect(returned_value).to eql(value)
expect(memoized_value).to eql({ [[1, 2, 3]] => value })
end
end
end
end
describe '#strong_memoized?' do
let(:value) { :anything }

View File

@ -337,31 +337,6 @@ RSpec.describe Issuable do
it { expect(MergeRequest.to_ability_name).to eq("merge_request") }
end
describe "#today?" do
it "returns true when created today" do
# Avoid timezone differences and just return exactly what we want
allow(Date).to receive(:today).and_return(issue.created_at.to_date)
expect(issue.today?).to be_truthy
end
it "returns false when not created today" do
allow(Date).to receive(:today).and_return(Date.yesterday)
expect(issue.today?).to be_falsey
end
end
describe "#new?" do
it "returns false when created 30 hours ago" do
allow(issue).to receive(:created_at).and_return(Time.current - 30.hours)
expect(issue.new?).to be_falsey
end
it "returns true when created 20 hours ago" do
allow(issue).to receive(:created_at).and_return(Time.current - 20.hours)
expect(issue.new?).to be_truthy
end
end
describe "#sort_by_attribute" do
let(:project) { create(:project) }

View File

@ -9,8 +9,8 @@ RSpec.describe ActiveHookFilter do
using RSpec::Parameterized::TableSyntax
context 'for various types of branch_filter' do
let_it_be_with_reload(:hook) do
create(:project_hook, push_events: true, issues_events: true)
let(:hook) do
build(:project_hook, push_events: true, issues_events: true)
end
where(:branch_filter_strategy, :branch_filter, :ref, :expected_matches?) do
@ -53,8 +53,8 @@ RSpec.describe ActiveHookFilter do
end
context 'when the branch filter is a invalid regex' do
let_it_be(:hook) do
create(
let(:hook) do
build(
:project_hook,
push_events: true,
push_events_branch_filter: 'master',
@ -70,8 +70,8 @@ RSpec.describe ActiveHookFilter do
end
context 'when the branch filter is not properly set to nil' do
let_it_be(:hook) do
create(
let(:hook) do
build(
:project_hook,
push_events: true,
branch_filter_strategy: 'all_branches'
@ -91,8 +91,8 @@ RSpec.describe ActiveHookFilter do
stub_feature_flags(enhanced_webhook_support_regex: false)
end
let_it_be(:hook) do
create(
let(:hook) do
build(
:project_hook,
push_events: true,
push_events_branch_filter: '(master)',

View File

@ -12,14 +12,14 @@ RSpec.describe ProjectHook do
end
it_behaves_like 'includes Limitable concern' do
subject { build(:project_hook, project: create(:project)) }
subject { build(:project_hook) }
end
describe '.for_projects' do
it 'finds related project hooks' do
hook_a = create(:project_hook)
hook_b = create(:project_hook)
hook_c = create(:project_hook)
hook_a = create(:project_hook, project: build(:project))
hook_b = create(:project_hook, project: build(:project))
hook_c = create(:project_hook, project: build(:project))
expect(described_class.for_projects([hook_a.project, hook_b.project]))
.to contain_exactly(hook_a, hook_b)
@ -30,16 +30,18 @@ RSpec.describe ProjectHook do
describe '.push_hooks' do
it 'returns hooks for push events only' do
hook = create(:project_hook, push_events: true)
create(:project_hook, push_events: false)
project = build(:project)
hook = create(:project_hook, project: project, push_events: true)
create(:project_hook, project: project, push_events: false)
expect(described_class.push_hooks).to eq([hook])
end
end
describe '.tag_push_hooks' do
it 'returns hooks for tag push events only' do
hook = create(:project_hook, tag_push_events: true)
create(:project_hook, tag_push_events: false)
project = build(:project)
hook = create(:project_hook, project: project, tag_push_events: true)
create(:project_hook, project: project, tag_push_events: false)
expect(described_class.tag_push_hooks).to eq([hook])
end
end
@ -65,7 +67,7 @@ RSpec.describe ProjectHook do
end
describe '#update_last_failure', :clean_gitlab_redis_shared_state do
let_it_be(:hook) { create(:project_hook) }
let(:hook) { build(:project_hook) }
it 'is a method of this class' do
expect { hook.update_last_failure }.not_to raise_error

View File

@ -32,10 +32,10 @@ RSpec.describe SystemHook do
end
describe "execute", :sidekiq_might_not_need_inline do
let(:system_hook) { create(:system_hook) }
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:group) { create(:group) }
let_it_be(:system_hook) { create(:system_hook) }
let_it_be(:user) { create(:user) }
let(:project) { build(:project, namespace: user.namespace) }
let(:group) { build(:group) }
let(:params) do
{ name: 'John Doe', username: 'jduser', email: 'jg@example.com', password: User.random_password }
end
@ -145,6 +145,7 @@ RSpec.describe SystemHook do
end
it 'group member update hook' do
group = create(:group)
group.add_guest(user)
group.add_maintainer(user)

View File

@ -12,7 +12,7 @@ RSpec.describe WebHookLog do
it { is_expected.to validate_presence_of(:web_hook) }
describe '.recent' do
let(:hook) { create(:project_hook) }
let(:hook) { build(:project_hook) }
it 'does not return web hook logs that are too old' do
create(:web_hook_log, web_hook: hook, created_at: 10.days.ago)
@ -30,8 +30,10 @@ RSpec.describe WebHookLog do
end
describe '#save' do
let(:hook) { build(:project_hook) }
context 'with basic auth credentials' do
let(:web_hook_log) { build(:web_hook_log, url: 'http://test:123@example.com') }
let(:web_hook_log) { build(:web_hook_log, web_hook: hook, url: 'http://test:123@example.com') }
subject { web_hook_log.save! }
@ -45,9 +47,9 @@ RSpec.describe WebHookLog do
end
context "with users' emails" do
let(:author) { create(:user) }
let(:user) { create(:user) }
let(:web_hook_log) { create(:web_hook_log, request_data: data) }
let(:author) { build(:user) }
let(:user) { build(:user) }
let(:web_hook_log) { create(:web_hook_log, web_hook: hook, request_data: data) }
let(:data) do
{
user: {
@ -93,11 +95,12 @@ RSpec.describe WebHookLog do
end
describe '.delete_batch_for' do
let(:hook) { create(:project_hook) }
let_it_be(:hook) { build(:project_hook) }
let_it_be(:hook2) { build(:project_hook) }
before do
before_all do
create_list(:web_hook_log, 3, web_hook: hook)
create_list(:web_hook_log, 3)
create_list(:web_hook_log, 3, web_hook: hook2)
end
subject { described_class.delete_batch_for(hook, batch_size: batch_size) }

View File

@ -198,7 +198,7 @@ RSpec.describe WebHook do
describe '.web_hooks_disable_failed?' do
it 'returns true when feature is enabled for parent' do
second_hook = build(:project_hook, project: create(:project))
second_hook = build(:project_hook)
stub_feature_flags(web_hooks_disable_failed: [false, second_hook.project])
expect(described_class.web_hooks_disable_failed?(hook)).to eq(false)

View File

@ -69,48 +69,6 @@ RSpec.describe API::Internal::Kubernetes do
context 'is authenticated for an agent' do
let!(:agent_token) { create(:cluster_agent_token) }
# Todo: Remove gitops_sync_count and k8s_api_proxy_request_count in the next milestone
# https://gitlab.com/gitlab-org/gitlab/-/issues/369489
# We're only keeping it for backwards compatibility until KAS is released
# using `counts:` instead
context 'deprecated events' do
it 'returns no_content for valid events' do
send_request(params: { gitops_sync_count: 10, k8s_api_proxy_request_count: 5 })
expect(response).to have_gitlab_http_status(:no_content)
end
it 'returns no_content for counts of zero' do
send_request(params: { gitops_sync_count: 0, k8s_api_proxy_request_count: 0 })
expect(response).to have_gitlab_http_status(:no_content)
end
it 'returns 400 for non number' do
send_request(params: { gitops_sync_count: 'string', k8s_api_proxy_request_count: 1 })
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'returns 400 for negative number' do
send_request(params: { gitops_sync_count: -1, k8s_api_proxy_request_count: 1 })
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'tracks events' do
counters = { gitops_sync_count: 10, k8s_api_proxy_request_count: 5 }
expected_counters = {
kubernetes_agent_gitops_sync: counters[:gitops_sync_count],
kubernetes_agent_k8s_api_proxy_request: counters[:k8s_api_proxy_request_count]
}
send_request(params: counters)
expect(Gitlab::UsageDataCounters::KubernetesAgentCounter.totals).to eq(expected_counters)
end
end
it 'returns no_content for valid events' do
counters = { gitops_sync: 10, k8s_api_proxy_request: 5 }
unique_counters = { agent_users_using_ci_tunnel: [10] }

View File

@ -2,7 +2,7 @@
RSpec.shared_examples 'includes Limitable concern' do
describe '#exceeds_limits?' do
let(:plan_limits) { create(:plan_limits, :default_plan) }
let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) }
context 'without plan limits configured' do
it { expect(subject.exceeds_limits?).to eq false }
@ -26,7 +26,7 @@ RSpec.shared_examples 'includes Limitable concern' do
end
describe 'validations' do
let(:plan_limits) { create(:plan_limits, :default_plan) }
let_it_be_with_reload(:plan_limits) { create(:plan_limits, :default_plan) }
it { is_expected.to be_a(Limitable) }