Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-20 09:10:30 +00:00
parent 763dd8a47a
commit b794758ce4
67 changed files with 632 additions and 662 deletions

View File

@ -4,8 +4,9 @@
.if-not-canonical-namespace: &if-not-canonical-namespace
if: '$CI_PROJECT_NAMESPACE !~ /^gitlab(-org)?($|\/)/'
.if-not-ee: &if-not-ee
if: '$CI_PROJECT_NAME !~ /^gitlab(-ee)?$/'
.exists-ee: &exists-ee
exists:
- ee/**/*
.if-not-foss: &if-not-foss
if: '$CI_PROJECT_NAME != "gitlab-foss" && $CI_PROJECT_NAME != "gitlab-ce" && $CI_PROJECT_NAME != "gitlabhq"'
@ -311,13 +312,14 @@
######################
.build-images:rules:build-qa-image:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-and-security-merge-request
<<: *exists-ee
changes: *ci-build-images-patterns
- <<: *if-dot-com-gitlab-org-and-security-merge-request
<<: *exists-ee
changes: *code-qa-patterns
- <<: *if-dot-com-gitlab-org-schedule
<<: *exists-ee
.build-images:rules:build-assets-image:
rules:
@ -363,9 +365,8 @@
.dev-fixtures:rules:ee-only:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-refs
<<: *exists-ee
changes: *code-backstage-patterns
when: on_success
@ -391,9 +392,8 @@
.graphql:rules:graphql-verify:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-refs
<<: *exists-ee
changes: *code-backstage-qa-patterns
when: on_success
@ -414,9 +414,8 @@
.frontend:rules:compile-test-assets-as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request # Always run for MRs since `compile-test-assets as-if-foss` is either needed by `rspec foss-impact` or the `rspec * as-if-foss` jobs.
<<: *exists-ee
changes: *code-backstage-qa-patterns
.frontend:rules:default-frontend-jobs:
@ -426,39 +425,37 @@
.frontend:rules:default-frontend-jobs-ee:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-refs
<<: *exists-ee
changes: *code-backstage-patterns
.frontend:rules:default-frontend-jobs-as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
- <<: *if-merge-request
<<: *exists-ee
changes: *ci-patterns
.frontend:rules:eslint-as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-title-as-if-foss
when: never
- <<: *if-merge-request
<<: *exists-ee
changes: *frontend-patterns
.frontend:rules:ee-mr-and-default-branch-only:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
when: always
- <<: *if-default-branch-refs
<<: *exists-ee
changes: *code-backstage-patterns
.frontend:rules:qa-frontend-node:
@ -499,9 +496,8 @@
###############
.pages:rules:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-branch-schedule-2-hourly
<<: *exists-ee
############
# QA rules #
@ -514,30 +510,34 @@
.qa:rules:as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *code-qa-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
- <<: *if-merge-request
<<: *exists-ee
changes: *ci-patterns
.qa:rules:package-and-qa:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-and-security-merge-request
<<: *exists-ee
changes: *ci-qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request
<<: *exists-ee
changes: *qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-and-security-merge-request
<<: *exists-ee
changes: *code-patterns
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule
<<: *exists-ee
allow_failure: true
###############
@ -649,15 +649,13 @@
.rails:rules:ee-only-migration:
rules:
- <<: *if-not-ee
when: never
- changes: *db-patterns
- <<: *exists-ee
changes: *db-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:ee-only-migration:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
@ -666,19 +664,18 @@
changes: *ci-patterns
when: never
- <<: *if-merge-request
<<: *exists-ee
changes: *db-patterns
.rails:rules:ee-only-unit:
rules:
- <<: *if-not-ee
when: never
- changes: *backend-patterns
- <<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:ee-only-unit:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
@ -687,19 +684,18 @@
changes: *ci-patterns
when: never
- <<: *if-merge-request
<<: *exists-ee
changes: *backend-patterns
.rails:rules:ee-only-integration:
rules:
- <<: *if-not-ee
when: never
- changes: *backend-patterns
- <<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:ee-only-integration:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
@ -708,19 +704,18 @@
changes: *ci-patterns
when: never
- <<: *if-merge-request
<<: *exists-ee
changes: *backend-patterns
.rails:rules:ee-only-system:
rules:
- <<: *if-not-ee
when: never
- changes: *code-backstage-patterns
- <<: *exists-ee
changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:ee-only-system:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request-title-run-all-rspec
@ -729,129 +724,143 @@
changes: *ci-patterns
when: never
- <<: *if-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
.rails:rules:as-if-foss-migration:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *db-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
changes: *db-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
- <<: *if-merge-request
<<: *exists-ee
changes: *ci-patterns
.rails:rules:as-if-foss-migration:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *db-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
changes: *db-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:as-if-foss-unit:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
- <<: *if-merge-request
<<: *exists-ee
changes: *ci-patterns
.rails:rules:as-if-foss-unit:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
.rails:rules:as-if-foss-integration:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
- <<: *if-merge-request
<<: *exists-ee
changes: *ci-patterns
.rails:rules:as-if-foss-integration:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
changes: *backend-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:as-if-foss-system:
rules:
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
- <<: *if-merge-request
<<: *exists-ee
changes: *ci-patterns
.rails:rules:as-if-foss-system:minimal:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-rspec-minimal-disabled
when: never
- <<: *if-merge-request
changes: *ci-patterns
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
changes: *code-backstage-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:ee-and-foss-db-library-code:
rules:
- changes: *db-library-patterns
- <<: *exists-ee
changes: *db-library-patterns
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:ee-mr-and-default-branch-only:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
- <<: *if-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
- <<: *if-default-branch-refs
<<: *exists-ee
changes: *code-backstage-patterns
.rails:rules:detect-tests:
@ -861,13 +870,13 @@
.rails:rules:rspec-foss-impact:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-title-as-if-foss
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
.rails:rules:rspec fail-fast:
@ -876,11 +885,11 @@
when: never
- <<: *if-rspec-fail-fast-skipped
when: never
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
.rails:rules:fail-pipeline-early:
@ -889,40 +898,41 @@
when: never
- <<: *if-rspec-fail-fast-skipped
when: never
- <<: *if-not-ee
when: never
- <<: *if-security-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
when: on_failure
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
when: on_failure
.rails:rules:deprecations:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-branch-schedule-nightly
<<: *exists-ee
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:rspec-coverage:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request
<<: *exists-ee
changes: *code-backstage-patterns
when: always
- <<: *if-default-branch-schedule-2-hourly
<<: *exists-ee
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
when: always
.rails:rules:rspec-feature-flags:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-branch-schedule-2-hourly
<<: *exists-ee
allow_failure: true
- <<: *if-merge-request-title-run-all-rspec
<<: *exists-ee
.rails:rules:default-branch-schedule-nightly--code-backstage:
rules:
@ -932,10 +942,10 @@
.rails:rules:default-branch-schedule-nightly--code-backstage-ee-only:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-branch-schedule-nightly
<<: *exists-ee
- <<: *if-merge-request
<<: *exists-ee
changes: [".gitlab/ci/rails.gitlab-ci.yml"]
#########################
@ -948,13 +958,14 @@
.static-analysis:rules:as-if-foss:
rules:
- <<: *if-not-ee
when: never
- <<: *if-merge-request-title-as-if-foss
<<: *exists-ee
changes: *code-backstage-qa-patterns
- <<: *if-security-merge-request
<<: *exists-ee
changes: *code-backstage-qa-patterns
- <<: *if-merge-request
<<: *exists-ee
changes: *ci-patterns
#######################
@ -1060,108 +1071,116 @@
################
.review:rules:review-build-cng:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *frontend-patterns
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-patterns
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule
<<: *exists-ee
.review:rules:review-deploy:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *frontend-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule
<<: *exists-ee
allow_failure: true
.review:rules:review-performance:
rules:
- if: '$DAST_RUN == "true"' # Skip this job when DAST is run
when: never
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *frontend-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-qa-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule
<<: *exists-ee
allow_failure: true
.review:rules:review-stop-failed-deployment:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-qa-patterns
.review:rules:review-qa-smoke:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *ci-review-patterns
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *frontend-patterns
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-qa-patterns
allow_failure: true
.review:rules:review-qa-all:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-patterns
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *qa-patterns
allow_failure: true
.review:rules:review-cleanup:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-qa-patterns
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule
<<: *exists-ee
allow_failure: true
.review:rules:review-stop:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-gitlab-org-merge-request
<<: *exists-ee
changes: *code-qa-patterns
when: manual
allow_failure: true
- <<: *if-dot-com-gitlab-org-schedule
<<: *exists-ee
allow_failure: true
.review:rules:danger:
@ -1204,9 +1223,8 @@
.setup:rules:verify-tests-yml:
rules:
- <<: *if-not-ee
when: never
- <<: *if-default-refs
<<: *exists-ee
changes: *code-backstage-patterns
when: on_success
@ -1221,12 +1239,12 @@
.test-metadata:rules:update-tests-metadata:
rules:
- <<: *if-not-ee
when: never
- changes:
- <<: *exists-ee
changes:
- ".gitlab/ci/test-metadata.gitlab-ci.yml"
- "scripts/rspec_helpers.sh"
- <<: *if-dot-com-ee-schedule
<<: *exists-ee
###################
# workhorse rules #

View File

@ -13,7 +13,6 @@
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/322903
Graphql/Descriptions:
Exclude:
- 'app/graphql/types/snippets/blob_action_enum.rb'
- 'ee/app/graphql/ee/types/list_limit_metric_enum.rb'
- 'ee/app/graphql/types/epic_state_enum.rb'
- 'ee/app/graphql/types/health_status_enum.rb'
@ -1655,21 +1654,17 @@ Gitlab/NamespacedClass:
- 'app/models/project_repository_storage_move.rb'
- 'app/models/project_services/alerts_service.rb'
- 'app/models/project_services/alerts_service_data.rb'
- 'app/models/project_services/bugzilla_service.rb'
- 'app/models/project_services/buildkite_service.rb'
- 'app/models/project_services/chat_notification_service.rb'
- 'app/models/project_services/ci_service.rb'
- 'app/models/project_services/custom_issue_tracker_service.rb'
- 'app/models/project_services/discord_service.rb'
- 'app/models/project_services/drone_ci_service.rb'
- 'app/models/project_services/ewm_service.rb'
- 'app/models/project_services/external_wiki_service.rb'
- 'app/models/project_services/flowdock_service.rb'
- 'app/models/project_services/hangouts_chat_service.rb'
- 'app/models/project_services/hipchat_service.rb'
- 'app/models/project_services/irker_service.rb'
- 'app/models/project_services/issue_tracker_data.rb'
- 'app/models/project_services/issue_tracker_service.rb'
- 'app/models/project_services/jenkins_service.rb'
- 'app/models/project_services/jira_service.rb'
- 'app/models/project_services/jira_tracker_data.rb'
@ -1679,21 +1674,18 @@ Gitlab/NamespacedClass:
- 'app/models/project_services/mock_ci_service.rb'
- 'app/models/project_services/mock_monitoring_service.rb'
- 'app/models/project_services/monitoring_service.rb'
- 'app/models/project_services/open_project_service.rb'
- 'app/models/project_services/open_project_tracker_data.rb'
- 'app/models/project_services/packagist_service.rb'
- 'app/models/project_services/pipelines_email_service.rb'
- 'app/models/project_services/pivotaltracker_service.rb'
- 'app/models/project_services/prometheus_service.rb'
- 'app/models/project_services/pushover_service.rb'
- 'app/models/project_services/redmine_service.rb'
- 'app/models/project_services/slack_service.rb'
- 'app/models/project_services/slack_slash_commands_service.rb'
- 'app/models/project_services/slash_commands_service.rb'
- 'app/models/project_services/teamcity_service.rb'
- 'app/models/project_services/unify_circuit_service.rb'
- 'app/models/project_services/webex_teams_service.rb'
- 'app/models/project_services/youtrack_service.rb'
- 'app/models/project_setting.rb'
- 'app/models/project_snippet.rb'
- 'app/models/project_statistics.rb'

View File

@ -152,7 +152,7 @@ Lint/MissingCopEnableDirective:
Lint/MixedRegexpCaptureTypes:
Exclude:
- 'app/models/alert_management/alert.rb'
- 'app/models/project_services/ewm_service.rb'
- 'app/models/integrations/ewm.rb'
- 'app/uploaders/file_uploader.rb'
- 'ee/lib/gitlab/code_owners/reference_extractor.rb'
- 'lib/gitlab/ci/pipeline/expression/lexeme/string.rb'

View File

@ -112,7 +112,7 @@ export default {
:empty-text="s__('AdminUsers|No users found')"
show-empty
stacked="md"
data-qa-selector="user_row_content"
:tbody-tr-attr="{ 'data-qa-selector': 'user_row_content' }"
>
<template #cell(name)="{ item: user }">
<user-avatar :user="user" :admin-user-path="paths.adminUser" />

View File

@ -6,10 +6,10 @@ module Types
graphql_name 'SnippetBlobActionEnum'
description 'Type of a snippet blob input action'
value 'create', value: :create
value 'update', value: :update
value 'delete', value: :delete
value 'move', value: :move
value 'create', description: 'Create a snippet blob.', value: :create
value 'update', description: 'Update a snippet blob.', value: :update
value 'delete', description: 'Delete a snippet blob.', value: :delete
value 'move', description: 'Move a snippet blob.', value: :move
end
end
end

View File

@ -29,7 +29,7 @@ module Mentionable
def self.external_pattern
strong_memoize(:external_pattern) do
issue_pattern = IssueTrackerService.reference_pattern
issue_pattern = Integrations::IssueTracker.reference_pattern
link_patterns = URI::DEFAULT_PARSER.make_regexp(%w(http https))
reference_pattern(link_patterns, issue_pattern)
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
module Integrations
class Bugzilla < IssueTracker
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
def title
'Bugzilla'
end
def description
s_("IssueTracker|Use Bugzilla as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/bugzilla'), target: '_blank', rel: 'noopener noreferrer'
s_("IssueTracker|Use Bugzilla as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'bugzilla'
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Integrations
class CustomIssueTracker < IssueTracker
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
def title
s_('IssueTracker|Custom issue tracker')
end
def description
s_("IssueTracker|Use a custom issue tracker as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/custom_issue_tracker'), target: '_blank', rel: 'noopener noreferrer'
s_('IssueTracker|Use a custom issue tracker that is not in the integration list. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'custom_issue_tracker'
end
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
module Integrations
class Ewm < IssueTracker
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
def self.reference_pattern(only_long: true)
@reference_pattern ||= %r{(?<issue>\b(bug|task|work item|workitem|rtcwi|defect)\b\s+\d+)}i
end
def title
'EWM'
end
def description
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/ewm'), target: '_blank', rel: 'noopener noreferrer'
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'ewm'
end
def can_test?
false
end
def issue_url(iid)
issues_url.gsub(':id', iid.to_s.split(' ')[-1])
end
end
end

View File

@ -0,0 +1,154 @@
# frozen_string_literal: true
module Integrations
class IssueTracker < Integration
validate :one_issue_tracker, if: :activated?, on: :manual_change
# TODO: we can probably just delegate as part of
# https://gitlab.com/gitlab-org/gitlab/issues/29404
data_field :project_url, :issues_url, :new_issue_url
default_value_for :category, 'issue_tracker'
before_validation :handle_properties
before_validation :set_default_data, on: :create
# Pattern used to extract links from comments
# Override this method on services that uses different patterns
# This pattern does not support cross-project references
# The other code assumes that this pattern is a superset of all
# overridden patterns. See ReferenceRegexes.external_pattern
def self.reference_pattern(only_long: false)
if only_long
/(\b[A-Z][A-Z0-9_]*-)#{Gitlab::Regex.issue}/
else
/(\b[A-Z][A-Z0-9_]*-|#{Issue.reference_prefix})#{Gitlab::Regex.issue}/
end
end
def handle_properties
# this has been moved from initialize_properties and should be improved
# as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
return unless properties
@legacy_properties_data = properties.dup
data_values = properties.slice!('title', 'description')
data_values.reject! { |key| data_fields.changed.include?(key) }
data_values.slice!(*data_fields.attributes.keys)
data_fields.assign_attributes(data_values) if data_values.present?
self.properties = {}
end
def legacy_properties_data
@legacy_properties_data ||= {}
end
def supports_data_fields?
true
end
def data_fields
issue_tracker_data || self.build_issue_tracker_data
end
def default?
default
end
def issue_url(iid)
issues_url.gsub(':id', iid.to_s)
end
def issue_tracker_path
project_url
end
def new_issue_path
new_issue_url
end
def issue_path(iid)
issue_url(iid)
end
def fields
[
{ type: 'text', name: 'project_url', title: _('Project URL'), help: s_('IssueTracker|The URL to the project in the external issue tracker.'), required: true },
{ type: 'text', name: 'issues_url', title: s_('IssueTracker|Issue URL'), help: s_('IssueTracker|The URL to view an issue in the external issue tracker. Must contain %{colon_id}.') % { colon_id: '<code>:id</code>'.html_safe }, required: true },
{ type: 'text', name: 'new_issue_url', title: s_('IssueTracker|New issue URL'), help: s_('IssueTracker|The URL to create an issue in the external issue tracker.'), required: true }
]
end
def initialize_properties
{}
end
# Initialize with default properties values
def set_default_data
return unless issues_tracker.present?
# we don't want to override if we have set something
return if project_url || issues_url || new_issue_url
data_fields.project_url = issues_tracker['project_url']
data_fields.issues_url = issues_tracker['issues_url']
data_fields.new_issue_url = issues_tracker['new_issue_url']
end
def self.supported_events
%w(push)
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
message = "#{self.type} was unable to reach #{self.project_url}. Check the url and try again."
result = false
begin
response = Gitlab::HTTP.head(self.project_url, verify: true)
if response
message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
result = true
end
rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, OpenSSL::SSL::SSLError => error
message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}"
end
log_info(message)
result
end
def support_close_issue?
false
end
def support_cross_reference?
false
end
private
def enabled_in_gitlab_config
Gitlab.config.issues_tracker &&
Gitlab.config.issues_tracker.values.any? &&
issues_tracker
end
def issues_tracker
Gitlab.config.issues_tracker[to_param]
end
def one_issue_tracker
return if template? || instance?
return if project.blank?
if project.integrations.external_issue_trackers.where.not(id: id).any?
errors.add(:base, _('Another issue tracker is already in use. Only one issue tracker service can be active at a time'))
end
end
end
end
Integrations::IssueTracker.prepend_mod_with('Integrations::IssueTracker')

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Integrations
class OpenProject < IssueTracker
validates :url, public_url: true, presence: true, if: :activated?
validates :api_url, public_url: true, allow_blank: true, if: :activated?
validates :token, presence: true, if: :activated?
validates :project_identifier_code, presence: true, if: :activated?
data_field :url, :api_url, :token, :closed_status_id, :project_identifier_code
def data_fields
open_project_tracker_data || self.build_open_project_tracker_data
end
def self.to_param
'open_project'
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Integrations
class Redmine < IssueTracker
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
def title
'Redmine'
end
def description
s_("IssueTracker|Use Redmine as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/redmine'), target: '_blank', rel: 'noopener noreferrer'
s_('IssueTracker|Use Redmine as the issue tracker. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'redmine'
end
end
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
module Integrations
class Youtrack < IssueTracker
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, presence: true, public_url: true, if: :activated?
# {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1, gl-030
def self.reference_pattern(only_long: false)
if only_long
/(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+\b)/
else
/(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+\b)|(#{Issue.reference_prefix}#{Gitlab::Regex.issue})/
end
end
def title
'YouTrack'
end
def description
s_("IssueTracker|Use YouTrack as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/youtrack'), target: '_blank', rel: 'noopener noreferrer'
s_("IssueTracker|Use YouTrack as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'youtrack'
end
def fields
[
{ type: 'text', name: 'project_url', title: _('Project URL'), help: s_('IssueTracker|The URL to the project in YouTrack.'), required: true },
{ type: 'text', name: 'issues_url', title: s_('ProjectService|Issue URL'), help: s_('IssueTracker|The URL to view an issue in the YouTrack project. Must contain %{colon_id}.') % { colon_id: '<code>:id</code>'.html_safe }, required: true }
]
end
end
end

View File

@ -9,6 +9,9 @@ class Label < ApplicationRecord
include Sortable
include FromUnion
include Presentable
include IgnorableColumns
ignore_column :remove_on_close, remove_with: '14.1', remove_after: '2021-06-22'
cache_markdown_field :description, pipeline: :single_line

View File

@ -186,13 +186,17 @@ class Project < ApplicationRecord
has_one :asana_service, class_name: 'Integrations::Asana'
has_one :assembla_service, class_name: 'Integrations::Assembla'
has_one :bamboo_service, class_name: 'Integrations::Bamboo'
has_one :bugzilla_service, class_name: 'Integrations::Bugzilla'
has_one :campfire_service, class_name: 'Integrations::Campfire'
has_one :confluence_service, class_name: 'Integrations::Confluence'
has_one :custom_issue_tracker_service, class_name: 'Integrations::CustomIssueTracker'
has_one :datadog_service, class_name: 'Integrations::Datadog'
has_one :emails_on_push_service, class_name: 'Integrations::EmailsOnPush'
has_one :ewm_service, class_name: 'Integrations::Ewm'
has_one :redmine_service, class_name: 'Integrations::Redmine'
has_one :youtrack_service, class_name: 'Integrations::Youtrack'
has_one :discord_service
has_one :drone_ci_service
has_one :ewm_service
has_one :pipelines_email_service
has_one :irker_service
has_one :pivotaltracker_service
@ -206,10 +210,6 @@ class Project < ApplicationRecord
has_one :pushover_service
has_one :jenkins_service
has_one :jira_service
has_one :redmine_service
has_one :youtrack_service
has_one :custom_issue_tracker_service
has_one :bugzilla_service
has_one :external_wiki_service
has_one :prometheus_service, inverse_of: :project
has_one :mock_ci_service

View File

@ -1,24 +0,0 @@
# frozen_string_literal: true
class BugzillaService < IssueTrackerService
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
def title
'Bugzilla'
end
def description
s_("IssueTracker|Use Bugzilla as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/bugzilla'), target: '_blank', rel: 'noopener noreferrer'
s_("IssueTracker|Use Bugzilla as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'bugzilla'
end
end

View File

@ -1,23 +0,0 @@
# frozen_string_literal: true
class CustomIssueTrackerService < IssueTrackerService
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
def title
s_('IssueTracker|Custom issue tracker')
end
def description
s_("IssueTracker|Use a custom issue tracker as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/custom_issue_tracker'), target: '_blank', rel: 'noopener noreferrer'
s_('IssueTracker|Use a custom issue tracker that is not in the integration list. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'custom_issue_tracker'
end
end

View File

@ -1,36 +0,0 @@
# frozen_string_literal: true
class EwmService < IssueTrackerService
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
def self.reference_pattern(only_long: true)
@reference_pattern ||= %r{(?<issue>\b(bug|task|work item|workitem|rtcwi|defect)\b\s+\d+)}i
end
def title
'EWM'
end
def description
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/ewm'), target: '_blank', rel: 'noopener noreferrer'
s_("IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'ewm'
end
def can_test?
false
end
def issue_url(iid)
issues_url.gsub(':id', iid.to_s.split(' ')[-1])
end
end

View File

@ -1,152 +0,0 @@
# frozen_string_literal: true
class IssueTrackerService < Integration
validate :one_issue_tracker, if: :activated?, on: :manual_change
# TODO: we can probably just delegate as part of
# https://gitlab.com/gitlab-org/gitlab/issues/29404
data_field :project_url, :issues_url, :new_issue_url
default_value_for :category, 'issue_tracker'
before_validation :handle_properties
before_validation :set_default_data, on: :create
# Pattern used to extract links from comments
# Override this method on services that uses different patterns
# This pattern does not support cross-project references
# The other code assumes that this pattern is a superset of all
# overridden patterns. See ReferenceRegexes.external_pattern
def self.reference_pattern(only_long: false)
if only_long
/(\b[A-Z][A-Z0-9_]*-)#{Gitlab::Regex.issue}/
else
/(\b[A-Z][A-Z0-9_]*-|#{Issue.reference_prefix})#{Gitlab::Regex.issue}/
end
end
def handle_properties
# this has been moved from initialize_properties and should be improved
# as part of https://gitlab.com/gitlab-org/gitlab/issues/29404
return unless properties
@legacy_properties_data = properties.dup
data_values = properties.slice!('title', 'description')
data_values.reject! { |key| data_fields.changed.include?(key) }
data_values.slice!(*data_fields.attributes.keys)
data_fields.assign_attributes(data_values) if data_values.present?
self.properties = {}
end
def legacy_properties_data
@legacy_properties_data ||= {}
end
def supports_data_fields?
true
end
def data_fields
issue_tracker_data || self.build_issue_tracker_data
end
def default?
default
end
def issue_url(iid)
issues_url.gsub(':id', iid.to_s)
end
def issue_tracker_path
project_url
end
def new_issue_path
new_issue_url
end
def issue_path(iid)
issue_url(iid)
end
def fields
[
{ type: 'text', name: 'project_url', title: _('Project URL'), help: s_('IssueTracker|The URL to the project in the external issue tracker.'), required: true },
{ type: 'text', name: 'issues_url', title: s_('IssueTracker|Issue URL'), help: s_('IssueTracker|The URL to view an issue in the external issue tracker. Must contain %{colon_id}.') % { colon_id: '<code>:id</code>'.html_safe }, required: true },
{ type: 'text', name: 'new_issue_url', title: s_('IssueTracker|New issue URL'), help: s_('IssueTracker|The URL to create an issue in the external issue tracker.'), required: true }
]
end
def initialize_properties
{}
end
# Initialize with default properties values
def set_default_data
return unless issues_tracker.present?
# we don't want to override if we have set something
return if project_url || issues_url || new_issue_url
data_fields.project_url = issues_tracker['project_url']
data_fields.issues_url = issues_tracker['issues_url']
data_fields.new_issue_url = issues_tracker['new_issue_url']
end
def self.supported_events
%w(push)
end
def execute(data)
return unless supported_events.include?(data[:object_kind])
message = "#{self.type} was unable to reach #{self.project_url}. Check the url and try again."
result = false
begin
response = Gitlab::HTTP.head(self.project_url, verify: true)
if response
message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}"
result = true
end
rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, OpenSSL::SSL::SSLError => error
message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}"
end
log_info(message)
result
end
def support_close_issue?
false
end
def support_cross_reference?
false
end
private
def enabled_in_gitlab_config
Gitlab.config.issues_tracker &&
Gitlab.config.issues_tracker.values.any? &&
issues_tracker
end
def issues_tracker
Gitlab.config.issues_tracker[to_param]
end
def one_issue_tracker
return if template? || instance?
return if project.blank?
if project.integrations.external_issue_trackers.where.not(id: id).any?
errors.add(:base, _('Another issue tracker is already in use. Only one issue tracker service can be active at a time'))
end
end
end
IssueTrackerService.prepend_mod_with('IssueTrackerService')

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
# Accessible as Project#external_issue_tracker
class JiraService < IssueTrackerService
class JiraService < Integrations::IssueTracker
extend ::Gitlab::Utils::Override
include Gitlab::Routing
include ApplicationHelper

View File

@ -1,18 +0,0 @@
# frozen_string_literal: true
class OpenProjectService < IssueTrackerService
validates :url, public_url: true, presence: true, if: :activated?
validates :api_url, public_url: true, allow_blank: true, if: :activated?
validates :token, presence: true, if: :activated?
validates :project_identifier_code, presence: true, if: :activated?
data_field :url, :api_url, :token, :closed_status_id, :project_identifier_code
def data_fields
open_project_tracker_data || self.build_open_project_tracker_data
end
def self.to_param
'open_project'
end
end

View File

@ -1,23 +0,0 @@
# frozen_string_literal: true
class RedmineService < IssueTrackerService
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
def title
'Redmine'
end
def description
s_("IssueTracker|Use Redmine as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/redmine'), target: '_blank', rel: 'noopener noreferrer'
s_('IssueTracker|Use Redmine as the issue tracker. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'redmine'
end
end

View File

@ -1,40 +0,0 @@
# frozen_string_literal: true
class YoutrackService < IssueTrackerService
include ActionView::Helpers::UrlHelper
validates :project_url, :issues_url, presence: true, public_url: true, if: :activated?
# {PROJECT-KEY}-{NUMBER} Examples: YT-1, PRJ-1, gl-030
def self.reference_pattern(only_long: false)
if only_long
/(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+\b)/
else
/(?<issue>\b[A-Za-z][A-Za-z0-9_]*-\d+\b)|(#{Issue.reference_prefix}#{Gitlab::Regex.issue})/
end
end
def title
'YouTrack'
end
def description
s_("IssueTracker|Use YouTrack as this project's issue tracker.")
end
def help
docs_link = link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/youtrack'), target: '_blank', rel: 'noopener noreferrer'
s_("IssueTracker|Use YouTrack as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
end
def self.to_param
'youtrack'
end
def fields
[
{ type: 'text', name: 'project_url', title: _('Project URL'), help: s_('IssueTracker|The URL to the project in YouTrack.'), required: true },
{ type: 'text', name: 'issues_url', title: s_('ProjectService|Issue URL'), help: s_('IssueTracker|The URL to view an issue in the YouTrack project. Must contain %{colon_id}.') % { colon_id: '<code>:id</code>'.html_safe }, required: true }
]
end
end

View File

@ -0,0 +1,5 @@
---
title: Add options events to Redis HLL metrics for filtering data
merge_request: 61685
author:
type: other

View File

@ -1,16 +1,18 @@
---
key_path: usage_activity_by_stage_monthly.create.action_monthly_active_users_design_management
description:
description: Monthly active users for design management
product_section: dev
product_stage: create
product_group: group::knowledge
product_stage: plan
product_group: group::product planning
product_category: design_management
value_type: number
status: data_available
time_frame: 28d
data_source:
data_source: redis_hll
distribution:
- ce
- ee
tier:
- free
skip_validation: true
- premium
- ultimate

View File

@ -9,7 +9,10 @@ value_type: number
status: data_available
time_frame: 28d
data_source: redis_hll
instrumentation_class: CountUsersUsingApproveQuickActionMetric
instrumentation_class: RedisHLLMetric
options:
events:
- i_quickactions_approve
distribution:
- ce
- ee

View File

@ -9,7 +9,10 @@ value_type: number
status: data_available
time_frame: 7d
data_source: redis_hll
instrumentation_class: CountUsersUsingApproveQuickActionMetric
instrumentation_class: RedisHLLMetric
options:
events:
- i_quickactions_approve
distribution:
- ce
- ee

View File

@ -1,16 +1,18 @@
---
key_path: counts.design_management_designs_create
description:
description: Number of designs that were created
product_section: dev
product_stage: create
product_group: group::knowledge
product_stage: plan
product_group: group::product planning
product_category: design_management
value_type: number
status: data_available
time_frame: all
data_source: database
data_source: redis
distribution:
- ce
- ee
tier:
- free
skip_validation: true
- premium
- ultimate

View File

@ -1,16 +1,18 @@
---
key_path: counts.design_management_designs_update
description:
description: Number of updates to designs
product_section: dev
product_stage: create
product_group: group::knowledge
product_stage: plan
product_group: group::product planning
product_category: design_management
value_type: number
status: data_available
time_frame: all
data_source: database
data_source: redis
distribution:
- ce
- ee
tier:
- free
skip_validation: true
- premium
- ultimate

View File

@ -1,16 +1,18 @@
---
key_path: counts.design_management_designs_delete
description:
description: Number of designs that were deleted
product_section: dev
product_stage: create
product_group: group::knowledge
product_stage: plan
product_group: group::product planning
product_category: design_management
value_type: number
status: data_available
time_frame: all
data_source: database
data_source: redis
distribution:
- ce
- ee
tier:
- free
skip_validation: true
- premium
- ultimate

View File

@ -43,7 +43,7 @@
"introduced_by_url": {
"type": ["string", "null"]
},
"extra": {
"options": {
"type": "object"
},
"time_frame": {
@ -56,7 +56,7 @@
},
"instrumentation_class": {
"type": "string",
"pattern": "^(([A-Z][a-z]+)+::)*(([A-Z][a-z]+)+)$"
"pattern": "^(([A-Z][a-z]+)+::)*(([A-Z]+[a-z]+)+)$"
},
"distribution": {
"type": "array",

View File

@ -14602,10 +14602,10 @@ Type of a snippet blob input action.
| Value | Description |
| ----- | ----------- |
| <a id="snippetblobactionenumcreate"></a>`create` | |
| <a id="snippetblobactionenumdelete"></a>`delete` | |
| <a id="snippetblobactionenummove"></a>`move` | |
| <a id="snippetblobactionenumupdate"></a>`update` | |
| <a id="snippetblobactionenumcreate"></a>`create` | Create a snippet blob. |
| <a id="snippetblobactionenumdelete"></a>`delete` | Delete a snippet blob. |
| <a id="snippetblobactionenummove"></a>`move` | Move a snippet blob. |
| <a id="snippetblobactionenumupdate"></a>`update` | Update a snippet blob. |
### `Sort`

View File

@ -758,7 +758,6 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
| `if:` conditions | Description | Notes |
|------------------|-------------|-------|
| `if-not-canonical-namespace` | Matches if the project isn't in the canonical (`gitlab-org/`) or security (`gitlab-org/security`) namespace. | Use to create a job for forks (by using `when: on_success|manual`), or **not** create a job for forks (by using `when: never`). |
| `if-not-ee` | Matches if the project isn't EE (i.e. project name isn't `gitlab` or `gitlab-ee`). | Use to create a job only in the FOSS project (by using `when: on_success|manual`), or **not** create a job if the project is EE (by using `when: never`). |
| `if-not-foss` | Matches if the project isn't FOSS (i.e. project name isn't `gitlab-foss`, `gitlab-ce`, or `gitlabhq`). | Use to create a job only in the EE project (by using `when: on_success|manual`), or **not** create a job if the project is FOSS (by using `when: never`). |
| `if-default-refs` | Matches if the pipeline is for `master`, `main`, `/^[\d-]+-stable(-ee)?$/` (stable branches), `/^\d+-\d+-auto-deploy-\d+$/` (auto-deploy branches), `/^security\//` (security branches), merge requests, and tags. | Note that jobs aren't created for branches with this default configuration. |
| `if-master-refs` | Matches if the current branch is `master` or `main`. | |
@ -788,6 +787,16 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
<!-- vale gitlab.Substitutions = YES -->
#### `exists:` conditions
<!-- vale gitlab.Substitutions = NO -->
| `exists:` conditions | Description | Notes |
|----------------------|-------------|-------|
| `exists-ee` | Matches if the project is EE (i.e. project contains `ee/` directory). | Use to create a job only in the EE project. |
<!-- vale gitlab.Substitutions = YES -->
#### `changes:` patterns
| `changes:` patterns | Description |

View File

@ -1020,39 +1020,39 @@ Tiers: `free`, `premium`, `ultimate`
### `counts.design_management_designs_create`
Missing description
Number of designs that were created
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216180740_design_management_designs_create.yml)
Group: `group::knowledge`
Group: `group::product planning`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `counts.design_management_designs_delete`
Missing description
Number of designs that were deleted
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216180743_design_management_designs_delete.yml)
Group: `group::knowledge`
Group: `group::product planning`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `counts.design_management_designs_update`
Missing description
Number of updates to designs
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210216180741_design_management_designs_update.yml)
Group: `group::knowledge`
Group: `group::product planning`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `counts.environments`
@ -2182,6 +2182,18 @@ Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_experience_0_sent`
Total sent emails of the experience track's first email
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210518081225_in_product_marketing_email_experience_0_sent.yml)
Group: `group::activation`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_team_0_cta_clicked`
Total clicks on the team track's first email
@ -2398,18 +2410,6 @@ Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_experience_0_sent`
Total sent emails of the experience track's first email
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210518081225_in_product_marketing_email_experience_0_sent.yml)
Group: `group::activation`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_review_folder`
Missing description
@ -11994,7 +11994,7 @@ Count of unique users to receive a notification while on-call
Group: `group::monitor`
Status: `implemented`
Status: `data_available`
Tiers: `premium`, `ultimate`
@ -12006,7 +12006,7 @@ Count of unique users to receive a notification while on-call
Group: `group::monitor`
Status: `implemented`
Status: `data_available`
Tiers: `premium`, `ultimate`
@ -17680,15 +17680,15 @@ Tiers: `free`, `premium`, `ultimate`
### `usage_activity_by_stage_monthly.create.action_monthly_active_users_design_management`
Missing description
Monthly active users for design management
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216180745_action_monthly_active_users_design_management.yml)
Group: `group::knowledge`
Group: `group::product planning`
Status: `data_available`
Tiers: `free`
Tiers: `free`, `premium`, `ultimate`
### `usage_activity_by_stage_monthly.create.action_monthly_active_users_git_write`

View File

@ -43,7 +43,7 @@ Each metric is defined in a separate YAML file consisting of a number of fields:
| `milestone` | no | The milestone when the metric is introduced. |
| `milestone_removed` | no | The milestone when the metric is removed. |
| `introduced_by_url` | no | The URL to the Merge Request that introduced the metric. |
| `extra` | no | `object`: extra information needed to calculate the metric value. |
| `options` | no | `object`: options information needed to calculate the metric value. |
| `skip_validation` | no | This should **not** be set. [Used for imported metrics until we review, update and make them valid](https://gitlab.com/groups/gitlab-org/-/epics/5425). |
### Metric statuses

View File

@ -26,7 +26,7 @@ A metric definition has the [`instrumentation_class`](metrics_dictionary.md) fie
The defined instrumentation class should have one of the existing metric classes: `DatabaseMetric`, `RedisHLLMetric`, or `GenericMetric`.
Using the instrumentation classes ensures that metrics can fail safe individually, without breaking the entire
Using the instrumentation classes ensures that metrics can fail safe individually, without breaking the entire
process of Usage Ping generation.
We have built a domain-specific language (DSL) to define the metrics instrumentation.
@ -53,20 +53,17 @@ end
## Redis HyperLogLog metrics
[Example of a merge request that adds a `RedisHLL` metric](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60089/diffs).
[Example of a merge request that adds a `RedisHLL` metric](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61685).
```ruby
module Gitlab
module Usage
module Metrics
module Instrumentations
class CountUsersUsingApproveQuickActionMetric < RedisHLLMetric
event_names :i_quickactions_approve
end
end
end
end
end
Count unique values for `i_quickactions_approve` event.
```yaml
time_frame: 28d
data_source: redis_hll
instrumentation_class: 'Gitlab::Usage::Metrics::Instrumentations::RedisHLLMetric'
options:
events:
- i_quickactions_approve
```
## Generic metrics

View File

@ -777,16 +777,18 @@ module API
::Integrations::Asana,
::Integrations::Assembla,
::Integrations::Bamboo,
::Integrations::Bugzilla,
::Integrations::Campfire,
::Integrations::Confluence,
::Integrations::CustomIssueTracker,
::Integrations::Datadog,
::Integrations::EmailsOnPush,
::BugzillaService,
::Integrations::Ewm,
::Integrations::Redmine,
::Integrations::Youtrack,
::BuildkiteService,
::CustomIssueTrackerService,
::DiscordService,
::DroneCiService,
::EwmService,
::ExternalWikiService,
::FlowdockService,
::HangoutsChatService,
@ -800,8 +802,6 @@ module API
::PivotaltrackerService,
::PrometheusService,
::PushoverService,
::RedmineService,
::YoutrackService,
::SlackService,
::MattermostService,
::MicrosoftTeamsService,

View File

@ -25,7 +25,7 @@ module Gitlab
# a proc that computes the sleep time given the number of preceding attempts
# (from 1 to retries - 1)
#
# Note: It's basically discouraged to use this method in a unicorn thread,
# Note: It's basically discouraged to use this method in a webserver thread,
# because this ties up all thread related resources until all `retries` are consumed.
# This could potentially eat up all connection pools.
def in_lock(key, ttl: 1.minute, retries: 10, sleep_sec: 0.01.seconds)

View File

@ -4,7 +4,8 @@ module Gitlab
module Integrations
class StiType < ActiveRecord::Type::String
NAMESPACED_INTEGRATIONS = Set.new(%w(
Asana Assembla Bamboo Campfire Confluence Datadog EmailsOnPush
Asana Assembla Bamboo Bugzilla Campfire Confluence CustomIssueTracker Datadog
EmailsOnPush Ewm IssueTracker Redmine Youtrack
)).freeze
def cast(value)

View File

@ -8,9 +8,11 @@ module Gitlab
include Gitlab::Utils::UsageData
attr_reader :time_frame
attr_reader :options
def initialize(time_frame:)
def initialize(time_frame:, options: {})
@time_frame = time_frame
@options = options
end
end
end

View File

@ -1,13 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Usage
module Metrics
module Instrumentations
class CountUsersUsingApproveQuickActionMetric < RedisHLLMetric
event_names :i_quickactions_approve
end
end
end
end
end

View File

@ -7,20 +7,24 @@ module Gitlab
class RedisHLLMetric < BaseMetric
# Usage example
#
# class CountUsersVisitingAnalyticsValuestreamMetric < RedisHLLMetric
# event_names :g_analytics_valuestream
# In metric YAML defintion
# instrumentation_class: RedisHLLMetric
# events:
# - g_analytics_valuestream
# end
class << self
def event_names(events = nil)
@metric_events = events
end
def initialize(time_frame:, options: {})
super
attr_reader :metric_events
raise ArgumentError, "options events are required" unless metric_events.present?
end
def metric_events
options[:events]
end
def value
redis_usage_data do
event_params = time_constraints.merge(event_names: self.class.metric_events)
event_params = time_constraints.merge(event_names: metric_events)
Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(**event_params)
end
@ -35,7 +39,7 @@ module Gitlab
when '7d'
{ start_date: 7.days.ago.to_date, end_date: Date.current }
else
raise "Unknown time frame: #{time_frame} for TimeConstraint"
raise "Unknown time frame: #{time_frame} for RedisHLLMetric"
end
end
end

View File

@ -7,9 +7,12 @@ module Gitlab
def uncached_data
::Gitlab::Usage::MetricDefinition.all.map do |definition|
instrumentation_class = definition.attributes[:instrumentation_class]
options = definition.attributes[:options]
if instrumentation_class.present?
metric_value = "Gitlab::Usage::Metrics::Instrumentations::#{instrumentation_class}".constantize.new(time_frame: definition.attributes[:time_frame]).value
metric_value = "Gitlab::Usage::Metrics::Instrumentations::#{instrumentation_class}".constantize.new(
time_frame: definition.attributes[:time_frame],
options: options).value
metric_payload(definition.key_path, metric_value)
else

View File

@ -9,13 +9,13 @@ module QA
Capybara.current_session
end
def confirm_user(username)
def confirm_user(user)
Flow::Login.while_signed_in_as_admin do
Page::Main::Menu.perform(&:go_to_admin_area)
Page::Admin::Menu.perform(&:go_to_users_overview)
Page::Admin::Overview::Users::Index.perform do |index|
index.search_user(username)
index.click_user(username)
index.search_user(user.email)
index.click_user(user.name)
end
Page::Admin::Overview::Users::Show.perform(&:confirm_user)

View File

@ -11,7 +11,7 @@ module QA
element :pending_approval_tab
end
view 'app/views/admin/users/_user.html.haml' do
view 'app/assets/javascripts/admin/users/components/users_table.vue' do
element :user_row_content
end

View File

@ -44,7 +44,7 @@ RSpec.describe Admin::ServicesController do
describe "#update" do
let(:project) { create(:project) }
let!(:service_template) do
RedmineService.create!(
Integrations::Redmine.create!(
project: nil,
active: false,
template: true,

View File

@ -6,7 +6,7 @@ FactoryBot.define do
type { 'Integration' }
end
factory :custom_issue_tracker_service, class: 'CustomIssueTrackerService' do
factory :custom_issue_tracker_service, class: 'Integrations::CustomIssueTracker' do
project
active { true }
issue_tracker
@ -85,25 +85,25 @@ FactoryBot.define do
confluence_url { 'https://example.atlassian.net/wiki' }
end
factory :bugzilla_service do
factory :bugzilla_service, class: 'Integrations::Bugzilla' do
project
active { true }
issue_tracker
end
factory :redmine_service do
factory :redmine_service, class: 'Integrations::Redmine' do
project
active { true }
issue_tracker
end
factory :youtrack_service do
factory :youtrack_service, class: 'Integrations::Youtrack' do
project
active { true }
issue_tracker
end
factory :ewm_service do
factory :ewm_service, class: 'Integrations::Ewm' do
project
active { true }
issue_tracker
@ -134,7 +134,7 @@ FactoryBot.define do
external_wiki_url { 'http://external-wiki-url.com' }
end
factory :open_project_service do
factory :open_project_service, class: 'Integrations::OpenProject' do
project
active { true }
@ -182,13 +182,13 @@ FactoryBot.define do
create_data { false }
after(:build) do
IssueTrackerService.skip_callback(:validation, :before, :handle_properties)
Integrations::IssueTracker.skip_callback(:validation, :before, :handle_properties)
end
to_create { |instance| instance.save!(validate: false) }
after(:create) do
IssueTrackerService.set_callback(:validation, :before, :handle_properties)
Integrations::IssueTracker.set_callback(:validation, :before, :handle_properties)
end
end

View File

@ -33,14 +33,6 @@ RSpec.describe Gitlab::GitalyClient do
it { expect(subject.long_timeout).to eq(6.hours) }
end
context 'running in Unicorn' do
before do
allow(Gitlab::Runtime).to receive(:unicorn?).and_return(true)
end
it { expect(subject.long_timeout).to eq(55) }
end
context 'running in Puma' do
before do
allow(Gitlab::Runtime).to receive(:puma?).and_return(true)

View File

@ -99,25 +99,6 @@ RSpec.describe Gitlab::Runtime do
end
end
context "unicorn" do
before do
stub_const('::Unicorn', Module.new)
stub_const('::Unicorn::HttpServer', Class.new)
stub_env('ACTION_CABLE_IN_APP', 'false')
end
it_behaves_like "valid runtime", :unicorn, 1
context "when ActionCable in-app mode is enabled" do
before do
stub_env('ACTION_CABLE_IN_APP', 'true')
stub_env('ACTION_CABLE_WORKER_POOL_SIZE', '3')
end
it_behaves_like "valid runtime", :unicorn, 4
end
end
context "sidekiq" do
let(:sidekiq_type) { double('::Sidekiq') }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountUsersUsingApproveQuickActionMetric, :clean_gitlab_redis_shared_state do
RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisHLLMetric, :clean_gitlab_redis_shared_state do
before do
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:i_quickactions_approve, values: 1, time: 1.week.ago)
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:i_quickactions_approve, values: 1, time: 2.weeks.ago)
@ -10,6 +10,10 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountUsersUsingApproveQ
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(:i_quickactions_approve, values: 2, time: 2.months.ago)
end
it_behaves_like 'a correct instrumented metric value', { time_frame: '28d', data_source: 'redis_hll' }, 2
it_behaves_like 'a correct instrumented metric value', { time_frame: '7d', data_source: 'redis_hll' }, 1
it_behaves_like 'a correct instrumented metric value', { time_frame: '28d', options: { events: ['i_quickactions_approve'] } }, 2
it_behaves_like 'a correct instrumented metric value', { time_frame: '7d', options: { events: ['i_quickactions_approve'] } }, 1
it 'raise exception if vents options is not present' do
expect { described_class.new(time_frame: '28d') }.to raise_error(ArgumentError)
end
end

View File

@ -95,7 +95,7 @@ RSpec.describe Gitlab::UsageData::Topology do
},
{
name: 'web',
server: 'unicorn'
server: 'puma'
}
]
}
@ -724,7 +724,7 @@ RSpec.describe Gitlab::UsageData::Topology do
},
# instance 2
{
'metric' => { 'instance' => 'instance2:8080', 'job' => 'gitlab-rails', 'server' => 'unicorn' },
'metric' => { 'instance' => 'instance2:8080', 'job' => 'gitlab-rails', 'server' => 'puma' },
'value' => [1000, '1']
}
])

View File

@ -159,7 +159,7 @@ RSpec.describe Integration do
context 'when instance-level service' do
Integration.available_services_types.each do |service_type|
let(:service) do
service_type.constantize.new(instance: true)
described_class.send(:service_type_to_model, service_type).new(instance: true)
end
it { is_expected.to be_falsey }
@ -169,7 +169,7 @@ RSpec.describe Integration do
context 'when group-level service' do
Integration.available_services_types.each do |service_type|
let(:service) do
service_type.constantize.new(group_id: group.id)
described_class.send(:service_type_to_model, service_type).new(group_id: group.id)
end
it { is_expected.to be_falsey }
@ -672,7 +672,7 @@ RSpec.describe Integration do
expect(described_class.service_name_to_model('asana')).to eq(Integrations::Asana)
# TODO We can remove this test when all models have been namespaced:
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60968#note_570994955
expect(described_class.service_name_to_model('youtrack')).to eq(YoutrackService)
expect(described_class.service_name_to_model('webex_teams')).to eq(WebexTeamsService)
end
it 'raises an error if service name is invalid' do
@ -802,7 +802,7 @@ RSpec.describe Integration do
describe 'initialize service with no properties' do
let(:service) do
BugzillaService.create!(
Integrations::Bugzilla.create!(
project: project,
project_url: 'http://gitlab.example.com'
)

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe BugzillaService do
RSpec.describe Integrations::Bugzilla do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe CustomIssueTrackerService do
RSpec.describe Integrations::CustomIssueTracker do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe EwmService do
RSpec.describe Integrations::Ewm do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }

View File

@ -2,12 +2,12 @@
require 'spec_helper'
RSpec.describe IssueTrackerService do
RSpec.describe Integrations::IssueTracker do
describe 'Validations' do
let(:project) { create :project }
describe 'only one issue tracker per project' do
let(:service) { RedmineService.new(project: project, active: true, issue_tracker_data: build(:issue_tracker_data)) }
let(:service) { Integrations::Redmine.new(project: project, active: true, issue_tracker_data: build(:issue_tracker_data)) }
before do
create(:custom_issue_tracker_service, project: project)

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe OpenProjectService do
RSpec.describe Integrations::OpenProject do
describe 'Validations' do
context 'when service is active' do
before do

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe RedmineService do
RSpec.describe Integrations::Redmine do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe YoutrackService do
RSpec.describe Integrations::Youtrack do
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }

View File

@ -1086,7 +1086,7 @@ RSpec.describe Project, factory_default: :keep do
project = create(:redmine_project)
expect(project).to receive(:integrations).once.and_call_original
2.times { expect(project.external_issue_tracker).to be_a_kind_of(RedmineService) }
2.times { expect(project.external_issue_tracker).to be_a_kind_of(Integrations::Redmine) }
end
end

View File

@ -156,15 +156,6 @@ RSpec.describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
context 'on Unicorn', :unicorn do
it 'updates the state' do
expect { request }.to change { Terraform::State.count }.by(0)
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
end
end
context 'without body' do
@ -200,15 +191,6 @@ RSpec.describe API::Terraform::State do
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
context 'on Unicorn', :unicorn do
it 'creates a new state' do
expect { request }.to change { Terraform::State.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
end
end
context 'without body' do

View File

@ -1,13 +1,14 @@
# frozen_string_literal: true
RSpec.shared_examples 'a correct instrumented metric value' do |options, expected_value|
let(:time_frame) { options[:time_frame] }
RSpec.shared_examples 'a correct instrumented metric value' do |params, expected_value|
let(:time_frame) { params[:time_frame] }
let(:options) { params[:options] }
before do
allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
end
it 'has correct value' do
expect(described_class.new(time_frame: time_frame).value).to eq(expected_value)
expect(described_class.new(time_frame: time_frame, options: options).value).to eq(expected_value)
end
end

View File

@ -294,16 +294,6 @@ RSpec.shared_examples 'rejects invalid upload_url params' do
end
end
RSpec.shared_examples 'successful response when using Unicorn' do
context 'on Unicorn', :unicorn do
it 'returns successfully' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
end
RSpec.shared_examples 'recipe snapshot endpoint' do
subject { get api(url), headers: headers }
@ -372,7 +362,6 @@ RSpec.shared_examples 'recipe upload_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
it_behaves_like 'successful response when using Unicorn'
it 'returns a set of upload urls for the files requested' do
subject
@ -434,7 +423,6 @@ RSpec.shared_examples 'package upload_urls endpoint' do
it_behaves_like 'rejects invalid recipe'
it_behaves_like 'rejects invalid upload_url params'
it_behaves_like 'successful response when using Unicorn'
it 'returns a set of upload urls for the files requested' do
expected_response = {

View File

@ -1,27 +0,0 @@
# frozen_string_literal: true
REQUEST_CLASSES = [
::Grape::Request,
::Rack::Request
].freeze
def request_body_class
return ::Unicorn::TeeInput if defined?(::Unicorn)
Class.new(StringIO) do
def string
raise NotImplementedError, '#string is only valid under Puma which uses StringIO, use #read instead'
end
end
end
RSpec.configure do |config|
config.before(:each, :unicorn) do
REQUEST_CLASSES.each do |request_class|
allow_any_instance_of(request_class)
.to receive(:body).and_wrap_original do |m, *args|
request_body_class.new(m.call(*args).read)
end
end
end
end

View File

@ -23,7 +23,7 @@ RSpec.describe 'projects/services/_form' do
end
it 'display merge_request_events and commit_events descriptions' do
allow(RedmineService).to receive(:supported_events).and_return(%w(commit merge_request))
allow(Integrations::Redmine).to receive(:supported_events).and_return(%w(commit merge_request))
render

View File

@ -11,7 +11,7 @@ All new configuration options that get added to Workhorse should go into the con
Options:
-apiCiLongPollingDuration duration
Long polling duration for job requesting for runners (default 50s - enabled) (default 50ns)
Long polling duration for job requesting for runners (default 50ns)
-apiLimit uint
Number of API requests allowed at single time
-apiQueueDuration duration

View File

@ -102,7 +102,7 @@ func buildConfig(arg0 string, args []string) (*bootConfig, *config.Config, error
fset.UintVar(&cfg.APILimit, "apiLimit", 0, "Number of API requests allowed at single time")
fset.UintVar(&cfg.APIQueueLimit, "apiQueueLimit", 0, "Number of API requests allowed to be queued")
fset.DurationVar(&cfg.APIQueueTimeout, "apiQueueDuration", queueing.DefaultTimeout, "Maximum queueing duration of requests")
fset.DurationVar(&cfg.APICILongPollingDuration, "apiCiLongPollingDuration", 50, "Long polling duration for job requesting for runners (default 50s - enabled)")
fset.DurationVar(&cfg.APICILongPollingDuration, "apiCiLongPollingDuration", 50, "Long polling duration for job requesting for runners")
fset.BoolVar(&cfg.PropagateCorrelationID, "propagateCorrelationID", false, "Reuse existing Correlation-ID from the incoming request header `X-Request-ID` if present")
if err := fset.Parse(args); err != nil {