diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-06 21:10:31 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-07-06 21:10:31 +0300 |
commit | eec8ec6e4ed7aea652141681749f82ca63e9fbf8 (patch) | |
tree | b643c4fa7894834830f2bc52f0005f8b7789dd14 | |
parent | 6d3676d61064af469f2fa1171bec4575235c6739 (diff) |
Add latest changes from gitlab-org/gitlab@master
187 files changed, 998 insertions, 640 deletions
diff --git a/.gitlab/ci/gitlab-gems.gitlab-ci.yml b/.gitlab/ci/gitlab-gems.gitlab-ci.yml index 0c2fc96b02b..f188e1f97f8 100644 --- a/.gitlab/ci/gitlab-gems.gitlab-ci.yml +++ b/.gitlab/ci/gitlab-gems.gitlab-ci.yml @@ -14,3 +14,6 @@ include: - local: .gitlab/ci/templates/gem.gitlab-ci.yml inputs: gem_name: "gitlab-schema-validation" + - local: .gitlab/ci/templates/gem.gitlab-ci.yml + inputs: + gem_name: "gitlab-ipynbdiff" diff --git a/.gitlab/ci/release-environments/main.gitlab-ci.yml b/.gitlab/ci/release-environments/main.gitlab-ci.yml index 6c28ba3e2dd..7eb67509301 100644 --- a/.gitlab/ci/release-environments/main.gitlab-ci.yml +++ b/.gitlab/ci/release-environments/main.gitlab-ci.yml @@ -14,7 +14,7 @@ review-build-cng: review-deploy-env: allow_failure: true stage: deploy - needs: ["release-environments-build-cng"] + needs: ["review-build-cng"] variables: DEPLOY_ENV: deploy.env script: diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml index 54dc4b4cb75..065a654eab2 100644 --- a/.gitlab/ci/review-apps/main.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml @@ -44,6 +44,12 @@ review-build-cng: variables: HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}" DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}" + GITLAB_HELM_CHART_PROJECT_URL: "https://gitlab.com/gitlab-org/charts/gitlab" + GITLAB_HELM_REPO_URL: "https://charts.gitlab.io" + GITLAB_REPO_URL: ${CI_PROJECT_URL} + GITLAB_IMAGE_REPOSITORY: "registry.gitlab.com/gitlab-org/build/cng-mirror" + GITLAB_IMAGE_SUFFIX: "ee" + GITLAB_VERIFY_DEPLOY_TIMEOUT_MINUTES: 5 GITLAB_HELM_CHART_REF: "75b1486a9aec212d0f49ef1251526d8e51004bbc" # 7.0.1: https://gitlab.com/gitlab-org/charts/gitlab/-/commit/75b1486a9aec212d0f49ef1251526d8e51004bbc environment: name: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # No separator for SCHEDULE_TYPE so it's compatible as before and looks nice without it diff --git a/.gitlab/ci/vendored-gems.gitlab-ci.yml b/.gitlab/ci/vendored-gems.gitlab-ci.yml index 3c9dadb8332..a06481197e5 100644 --- a/.gitlab/ci/vendored-gems.gitlab-ci.yml +++ b/.gitlab/ci/vendored-gems.gitlab-ci.yml @@ -13,10 +13,6 @@ include: gem_path_prefix: "vendor/gems/" - local: .gitlab/ci/templates/gem.gitlab-ci.yml inputs: - gem_name: "ipynbdiff" - gem_path_prefix: "vendor/gems/" - - local: .gitlab/ci/templates/gem.gitlab-ci.yml - inputs: gem_name: "omniauth-azure-oauth2" gem_path_prefix: "vendor/gems/" - local: .gitlab/ci/templates/gem.gitlab-ci.yml diff --git a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md index ae5c6a054c0..24ccd4afd6d 100644 --- a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md +++ b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md @@ -146,11 +146,14 @@ The Geo primary site needs to checksum every replicable so secondaries can verif enable_lock_retries! def up - create_table :cool_widget_states, id: false do |t| + create_table :cool_widget_states do |t| t.datetime_with_timezone :verification_started_at t.datetime_with_timezone :verification_retry_at t.datetime_with_timezone :verified_at - t.references :cool_widget, primary_key: true, default: nil, index: false, foreign_key: { on_delete: :cascade } + t.references :cool_widget, + null: false, + index: { unique: true }, + foreign_key: { on_delete: :cascade } t.integer :verification_state, default: 0, limit: 2, null: false t.integer :verification_retry_count, default: 0, limit: 2, null: false t.binary :verification_checksum, using: 'verification_checksum::bytea' @@ -292,6 +295,11 @@ That's all of the required database changes. # Search the codebase for examples, and consult a Geo expert if needed. end + override :verification_state_model_key + def verification_state_model_key + :cool_widget_id + end + override :verification_state_table_class def verification_state_table_class CoolWidgetState diff --git a/.gitlab/issue_templates/Geo Replicate a new blob type.md b/.gitlab/issue_templates/Geo Replicate a new blob type.md index 91a86a96ac9..94c93bd27e4 100644 --- a/.gitlab/issue_templates/Geo Replicate a new blob type.md +++ b/.gitlab/issue_templates/Geo Replicate a new blob type.md @@ -148,14 +148,13 @@ The Geo primary site needs to checksum every replicable so secondaries can verif enable_lock_retries! def up - create_table :cool_widget_states, id: false do |t| + create_table :cool_widget_states do |t| t.datetime_with_timezone :verification_started_at t.datetime_with_timezone :verification_retry_at t.datetime_with_timezone :verified_at t.references :cool_widget, - primary_key: true, - default: nil, - index: false, + null: false, + index: { unique: true }, foreign_key: { on_delete: :cascade } t.integer :verification_state, default: 0, limit: 2, null: false t.integer :verification_retry_count, default: 0, limit: 2, null: false @@ -298,6 +297,11 @@ That's all of the required database changes. # Search the codebase for examples, and consult a Geo expert if needed. end + override :verification_state_model_key + def verification_state_model_key + :cool_widget_id + end + override :verification_state_table_class def verification_state_table_class CoolWidgetState diff --git a/.rubocop_todo/naming/inclusive_language.yml b/.rubocop_todo/naming/inclusive_language.yml index 350df577e09..9d1e98f87d3 100644 --- a/.rubocop_todo/naming/inclusive_language.yml +++ b/.rubocop_todo/naming/inclusive_language.yml @@ -28,7 +28,6 @@ Naming/InclusiveLanguage: - 'ee/app/controllers/projects/push_rules_controller.rb' - 'ee/lib/arkose/verify_response.rb' - 'ee/lib/system_check/geo/http_connection_check.rb' - - 'ee/spec/lib/gitlab/checks/diff_check_spec.rb' - 'ee/spec/models/dora/lead_time_for_changes_metric_spec.rb' - 'lib/api/entities/application_setting.rb' - 'lib/api/settings.rb' @@ -45,31 +44,18 @@ Naming/InclusiveLanguage: - 'lib/gitlab/sanitizers/svg.rb' - 'lib/gitlab/sanitizers/svg/whitelist.rb' - 'lib/system_check/app/git_user_default_ssh_config_check.rb' - - 'rubocop/cop/active_record_association_reload.rb' - - 'rubocop/cop/avoid_becomes.rb' - - 'rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb' - 'rubocop/cop/avoid_return_from_blocks.rb' - - 'rubocop/cop/default_scope.rb' - - 'rubocop/cop/destroy_all.rb' - 'rubocop/cop/graphql/id_type.rb' - - 'rubocop/cop/group_public_or_visible_to_user.rb' - - 'rubocop/cop/inject_enterprise_edition_module.rb' - - 'rubocop/cop/migration/add_columns_to_wide_tables.rb' - 'spec/controllers/concerns/issuable_collections_spec.rb' - 'spec/controllers/health_check_controller_spec.rb' - 'spec/controllers/metrics_controller_spec.rb' - - 'spec/features/projects/import_export/export_file_spec.rb' - 'spec/helpers/markup_helper_spec.rb' - 'spec/lib/banzai/filter/asset_proxy_filter_spec.rb' - 'spec/lib/gitlab/asset_proxy_spec.rb' - 'spec/lib/gitlab/auth/ip_rate_limiter_spec.rb' - 'spec/lib/gitlab/git/hook_env_spec.rb' - 'spec/lib/gitlab/github_import/markdown/attachment_spec.rb' - - 'spec/lib/gitlab/import_export/attribute_configuration_spec.rb' - - 'spec/lib/gitlab/import_export/references_configuration_spec.rb' - - 'spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb' - 'spec/lib/gitlab/middleware/basic_health_check_spec.rb' - - 'spec/lib/gitlab/middleware/go_spec.rb' - 'spec/lib/gitlab/sanitizers/exif_spec.rb' - 'spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb' - 'spec/models/application_setting_spec.rb' @@ -593,7 +593,7 @@ gem 'ipaddress', '~> 0.8.3' gem 'parslet', '~> 1.8' -gem 'ipynbdiff', path: 'vendor/gems/ipynbdiff' +gem 'ipynbdiff', path: 'gems/ipynbdiff', require: 'ipynb_diff' gem 'ed25519', '~> 1.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index a2a7a3f26ae..adbca941778 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -35,6 +35,13 @@ PATH rake (~> 13.0) PATH + remote: gems/ipynbdiff + specs: + ipynbdiff (0.4.7) + diffy (~> 3.4) + oj (~> 3.13.16) + +PATH remote: vendor/gems/attr_encrypted specs: attr_encrypted (3.2.4) @@ -69,13 +76,6 @@ PATH typhoeus (~> 1.0, >= 1.0.1) PATH - remote: vendor/gems/ipynbdiff - specs: - ipynbdiff (0.4.7) - diffy (~> 3.4) - oj (~> 3.13.16) - -PATH remote: vendor/gems/mail-smtp_pool specs: mail-smtp_pool (0.1.0) diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue index e88e0a3a344..033f48827f1 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_form.vue @@ -122,14 +122,12 @@ export default { name: '', token: '', url: '', - apiUrl: '', }, activeTabIndex: this.tabIndex, currentIntegration: null, parsedPayload: [], validationState: { name: true, - apiUrl: true, }, pricingLink: `${PROMO_URL}/pricing`, }; @@ -188,20 +186,14 @@ export default { ); }, isFormDirty() { - const { type, active, name, apiUrl, payloadAlertFields = [], payloadAttributeMappings = [] } = + const { type, active, name, payloadAlertFields = [], payloadAttributeMappings = [] } = this.currentIntegration || {}; - const { - name: formName, - apiUrl: formApiUrl, - active: formActive, - type: formType, - } = this.integrationForm; + const { name: formName, active: formActive, type: formType } = this.integrationForm; const isDirty = type !== formType || active !== formActive || name !== formName || - apiUrl !== formApiUrl || !isEqual(this.parsedPayload, payloadAlertFields) || !isEqual(this.mapping, this.getCleanMapping(payloadAttributeMappings)); @@ -211,25 +203,19 @@ export default { return this.isFormValid && this.isFormDirty; }, dataForSave() { - const { name, apiUrl, active } = this.integrationForm; + const { name, active } = this.integrationForm; const customMappingVariables = { payloadAttributeMappings: this.mapping, payloadExample: this.samplePayload.json || '{}', }; - const variables = this.isHttp - ? { name, active, ...customMappingVariables } - : { apiUrl, active }; + const variables = this.isHttp ? { name, active, ...customMappingVariables } : { active }; return { type: this.integrationForm.type, variables }; }, testAlertModal() { return this.isFormDirty ? testAlertModalId : null; }, - prometheusUrlInvalidFeedback() { - const { blankUrlError, invalidUrlError } = i18n.integrationFormSteps.prometheusFormUrl; - return this.integrationForm.apiUrl?.length ? invalidUrlError : blankUrlError; - }, }, watch: { tabIndex(val) { @@ -247,13 +233,12 @@ export default { type, active, url, - apiUrl, token, payloadExample, payloadAlertFields, payloadAttributeMappings, } = val; - this.integrationForm = { type, name, active, url, apiUrl, token }; + this.integrationForm = { type, name, active, url, token }; if (this.showMappingBuilder) { this.resetPayloadAndMappingConfirmed = false; @@ -271,14 +256,6 @@ export default { validateName() { this.validationState.name = Boolean(this.integrationForm.name?.length); }, - validateApiUrl() { - try { - const parsedUrl = new URL(this.integrationForm.apiUrl); - this.validationState.apiUrl = ['http:', 'https:'].includes(parsedUrl.protocol); - } catch (e) { - this.validationState.apiUrl = false; - } - }, isValidNonEmptyJSON(JSONString) { if (JSONString) { let parsed; @@ -298,14 +275,12 @@ export default { }, triggerValidation() { if (this.isHttp) { - this.validationState.apiUrl = true; this.validateName(); if (!this.validationState.name) { this.$refs.integrationName.$el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } else if (this.isPrometheus) { this.validationState.name = true; - this.validateApiUrl(); } }, sendTestAlert() { @@ -332,7 +307,6 @@ export default { this.integrationForm.type = integrationTypes.none.value; this.integrationForm.name = ''; this.integrationForm.active = false; - this.integrationForm.apiUrl = ''; this.samplePayload = { json: null, error: null, @@ -490,28 +464,6 @@ export default { class="gl-mt-4 gl-font-weight-normal" /> </gl-form-group> - - <gl-form-group - v-if="isPrometheus" - class="gl-my-4" - :label="$options.i18n.integrationFormSteps.prometheusFormUrl.label" - label-for="api-url" - :invalid-feedback="prometheusUrlInvalidFeedback" - :state="validationState.apiUrl" - > - <gl-form-input - id="api-url" - v-model="integrationForm.apiUrl" - type="text" - :placeholder="$options.placeholders.prometheus" - data-qa-selector="prometheus_url_field" - @input="validateApiUrl" - /> - <span class="gl-text-gray-400"> - {{ $options.i18n.integrationFormSteps.prometheusFormUrl.help }} - </span> - </gl-form-group> - <template v-if="showMappingBuilder"> <gl-form-group data-testid="sample-payload-section" diff --git a/app/assets/javascripts/alerts_settings/constants.js b/app/assets/javascripts/alerts_settings/constants.js index 6d914fe8361..218b09cb1b6 100644 --- a/app/assets/javascripts/alerts_settings/constants.js +++ b/app/assets/javascripts/alerts_settings/constants.js @@ -65,12 +65,6 @@ export const i18n = { proceedWithoutSave: s__('AlertSettings|Send without saving'), cancel: __('Cancel'), }, - prometheusFormUrl: { - label: s__('AlertSettings|Prometheus API base URL'), - help: s__('AlertSettings|URL cannot be blank and must start with http: or https:.'), - blankUrlError: __('URL cannot be blank'), - invalidUrlError: __('URL is invalid'), - }, restKeyInfo: { label: s__( 'AlertSettings|If you reset the authorization key for this project, you must update the key in every enabled alert source.', diff --git a/app/assets/javascripts/alerts_settings/graphql/fragments/integration_item.fragment.graphql b/app/assets/javascripts/alerts_settings/graphql/fragments/integration_item.fragment.graphql index 6d9307959df..2d8430dbede 100644 --- a/app/assets/javascripts/alerts_settings/graphql/fragments/integration_item.fragment.graphql +++ b/app/assets/javascripts/alerts_settings/graphql/fragments/integration_item.fragment.graphql @@ -5,5 +5,4 @@ fragment IntegrationItem on AlertManagementIntegration { name url token - apiUrl } diff --git a/app/assets/javascripts/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql b/app/assets/javascripts/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql index bb22795ddd5..c2acd928c5c 100644 --- a/app/assets/javascripts/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql +++ b/app/assets/javascripts/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql @@ -1,9 +1,7 @@ #import "../fragments/integration_item.fragment.graphql" -mutation createPrometheusIntegration($projectPath: ID!, $apiUrl: String!, $active: Boolean!) { - prometheusIntegrationCreate( - input: { projectPath: $projectPath, apiUrl: $apiUrl, active: $active } - ) { +mutation createPrometheusIntegration($projectPath: ID!, $active: Boolean!) { + prometheusIntegrationCreate(input: { projectPath: $projectPath, active: $active }) { errors integration { ...IntegrationItem diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue index 732d544816b..c4675c25df4 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue @@ -4,9 +4,9 @@ import { GlButton, GlDropdown, GlDropdownItem, - GlEmptyState, GlFormGroup, GlFormInputGroup, + GlSkeletonLoader, GlModal, GlModalDirective, GlSprintf, @@ -17,7 +17,6 @@ import Api from '~/api'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue'; -import { DEPENDENCY_PROXY_DOCS_PATH } from '~/packages_and_registries/settings/group/constants'; import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants'; import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql'; @@ -28,7 +27,7 @@ export default { GlButton, GlDropdown, GlDropdownItem, - GlEmptyState, + GlSkeletonLoader, GlFormGroup, GlFormInputGroup, GlModal, @@ -41,13 +40,12 @@ export default { GlModalDirective, GlTooltip: GlTooltipDirective, }, - inject: ['groupPath', 'groupId', 'noManifestsIllustration', 'canClearCache', 'settingsPath'], + inject: ['groupPath', 'groupId', 'canClearCache', 'settingsPath'], i18n: { proxyImagePrefix: s__('DependencyProxy|Dependency Proxy image prefix'), copyImagePrefixText: s__('DependencyProxy|Copy prefix'), blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'), pageTitle: s__('DependencyProxy|Dependency Proxy'), - noManifestTitle: s__('DependencyProxy|There are no images in the cache'), deleteCacheAlertMessageSuccess: s__( 'DependencyProxy|All items in the cache are scheduled for removal.', ), @@ -64,9 +62,6 @@ export default { text: __('Cancel'), }, }, - links: { - DEPENDENCY_PROXY_DOCS_PATH, - }, data() { return { group: {}, @@ -90,7 +85,7 @@ export default { return this.group.dependencyProxyManifests?.pageInfo; }, manifests() { - return this.group.dependencyProxyManifests?.nodes; + return this.group.dependencyProxyManifests?.nodes ?? []; }, modalTitleWithCount() { return sprintf( @@ -199,10 +194,18 @@ export default { </template> </title-area> - <gl-form-group v-if="showDependencyProxyImagePrefix" :label="$options.i18n.proxyImagePrefix"> + <gl-skeleton-loader v-if="$apollo.queries.group.loading" /> + + <gl-form-group + v-if="showDependencyProxyImagePrefix" + :label="$options.i18n.proxyImagePrefix" + label-for="proxy-url" + > <gl-form-input-group + id="proxy-url" readonly :value="group.dependencyProxyImagePrefix" + select-on-click class="gl-layout-w-limited" data-testid="proxy-url" > @@ -224,7 +227,6 @@ export default { </gl-form-group> <manifests-list - v-if="manifests && manifests.length" :dependency-proxy-image-prefix="dependencyProxyImagePrefix" :loading="$apollo.queries.group.loading" :manifests="manifests" @@ -233,12 +235,6 @@ export default { @next-page="fetchNextPage" /> - <gl-empty-state - v-else - :svg-path="noManifestsIllustration" - :title="$options.i18n.noManifestTitle" - /> - <gl-modal :modal-id="$options.confirmClearCacheModal" :title="modalTitleWithCount" diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_empty_state.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_empty_state.vue new file mode 100644 index 00000000000..b0d03a7cebe --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_empty_state.vue @@ -0,0 +1,80 @@ +<script> +import { GlEmptyState, GlFormGroup, GlFormInputGroup, GlLink, GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { DEPENDENCY_PROXY_HELP_PAGE_PATH } from '~/packages_and_registries/dependency_proxy/constants'; + +export default { + name: 'ManifestsEmptyState', + components: { + ClipboardButton, + GlEmptyState, + GlFormGroup, + GlFormInputGroup, + GlLink, + GlSprintf, + }, + inject: ['noManifestsIllustration'], + i18n: { + codeExampleLabel: s__('DependencyProxy|Pull image by digest example'), + noManifestTitle: s__('DependencyProxy|There are no images in the cache'), + emptyText: s__( + 'DependencyProxy|To store docker images in Dependency Proxy cache, pull an image by tag in your %{codeStart}.gitlab-ci.yml%{codeEnd} file. In this example, the image is %{codeStart}alpine:latest%{codeEnd}', + ), + documentationText: s__( + 'DependencyProxy|%{docLinkStart}See the documentation%{docLinkEnd} for other ways to store Docker images in Dependency Proxy cache.', + ), + copyExample: s__('DependencyProxy|Copy example'), + }, + // eslint-disable-next-line no-template-curly-in-string + codeExample: 'image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/alpine:latest', + links: { + DEPENDENCY_PROXY_HELP_PAGE_PATH, + }, +}; +</script> + +<template> + <gl-empty-state :svg-path="noManifestsIllustration" :title="$options.i18n.noManifestTitle"> + <template #description> + <p class="gl-mb-5"> + <gl-sprintf :message="$options.i18n.emptyText"> + <template #code="{ content }"> + <code>{{ content }}</code> + </template> + </gl-sprintf> + </p> + + <gl-form-group + class="gl-mb-5" + :label="$options.i18n.codeExampleLabel" + label-for="code-example" + label-sr-only + > + <gl-form-input-group + id="code-example" + readonly + :value="$options.codeExample" + class="gl-w-70p gl-mx-auto" + select-on-click + > + <template #append> + <clipboard-button + :text="$options.codeExample" + :title="$options.i18n.copyExample" + class="gl-m-0!" + /> + </template> + </gl-form-input-group> + </gl-form-group> + + <p> + <gl-sprintf :message="$options.i18n.documentationText"> + <template #docLink="{ content }"> + <gl-link :href="$options.links.DEPENDENCY_PROXY_HELP_PAGE_PATH">{{ content }}</gl-link> + </template> + </gl-sprintf> + </p> + </template> + </gl-empty-state> +</template> diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue index 9870841f1ff..94c958308dd 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifests_list.vue @@ -2,11 +2,13 @@ import { GlKeysetPagination, GlSkeletonLoader } from '@gitlab/ui'; import { s__ } from '~/locale'; import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue'; +import ManifestsEmptyState from '~/packages_and_registries/dependency_proxy/components/manifests_empty_state.vue'; export default { name: 'ManifestsLists', components: { ManifestRow, + ManifestsEmptyState, GlKeysetPagination, GlSkeletonLoader, }, @@ -18,7 +20,8 @@ export default { }, pagination: { type: Object, - required: true, + required: false, + default: () => ({}), }, loading: { type: Boolean, @@ -44,12 +47,18 @@ export default { <template> <div class="gl-mt-6"> - <h3 class="gl-font-base">{{ $options.i18n.listTitle }}</h3> - <gl-skeleton-loader v-if="loading" /> + <h3 class="gl-font-base gl-pb-3 gl-mb-0 gl-border-b-1 gl-border-gray-100 gl-border-b-solid"> + {{ $options.i18n.listTitle }} + </h3> + + <div v-if="loading" class="gl-py-3"> + <gl-skeleton-loader /> + </div> + + <manifests-empty-state v-else-if="manifests.length === 0" /> + <div v-else data-testid="main-area"> - <div - class="gl-border-t-1 gl-border-gray-100 gl-border-t-solid gl-display-flex gl-flex-direction-column" - > + <div class="gl-display-flex gl-flex-direction-column"> <manifest-row v-for="(manifest, index) in manifests" :key="index" diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js index fdad69204ba..8e88df92155 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js @@ -1,2 +1,11 @@ +import { helpPagePath } from '~/helpers/help_page_helper'; + export const GRAPHQL_PAGE_SIZE = 20; export const MANIFEST_PENDING_DESTRUCTION_STATUS = 'PENDING_DESTRUCTION'; + +export const DEPENDENCY_PROXY_HELP_PAGE_PATH = helpPagePath( + 'user/packages/dependency_proxy/index', + { + anchor: 'store-a-docker-image-in-dependency-proxy-cache', + }, +); diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue index 9b4e5d471d6..d8b843bdfb0 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue @@ -13,7 +13,7 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { __, sprintf } from '~/locale'; import CancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql'; import RetryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql'; -import CiStatus from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { reportToSentry } from '../../utils'; import { ACTION_FAILURE, DOWNSTREAM, UPSTREAM } from './constants'; @@ -22,7 +22,7 @@ export default { GlTooltip: GlTooltipDirective, }, components: { - CiStatus, + CiIcon, GlBadge, GlButton, GlLink, @@ -240,7 +240,7 @@ export default { </gl-tooltip> <div class="gl-bg-white gl-border gl-p-3 gl-rounded-lg gl-w-full" :class="cardClasses"> <div class="gl-display-flex gl-gap-x-3"> - <ci-status v-if="!pipelineIsLoading" :status="pipelineStatus" :size="24" css-classes="" /> + <ci-icon v-if="!pipelineIsLoading" :status="pipelineStatus" :size="24" /> <div v-else class="gl-pr-3"><gl-loading-icon size="sm" inline /></div> <div class="gl-display-flex gl-downstream-pipeline-job-width gl-flex-direction-column gl-line-height-normal" diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 3c96e49499f..b75ca2649c3 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -159,7 +159,7 @@ class Admin::UsersController < Admin::ApplicationController end def unlock - if update_user(&:unlock_access!) + if unlock_user redirect_back_or_admin_user(notice: _("Successfully unlocked")) else redirect_back_or_admin_user(alert: _("Error occurred. User was not unlocked")) @@ -401,6 +401,11 @@ class Admin::UsersController < Admin::ApplicationController _("You cannot impersonate a user who cannot log in") end end + + # method overriden in EE + def unlock_user + update_user(&:unlock_access!) + end end Admin::UsersController.prepend_mod_with('Admin::UsersController') diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb index 0ad8a08960a..a326fa308ad 100644 --- a/app/controllers/concerns/issuable_actions.rb +++ b/app/controllers/concerns/issuable_actions.rb @@ -12,6 +12,7 @@ module IssuableActions before_action :authorize_destroy_issuable!, only: :destroy before_action :check_destroy_confirmation!, only: :destroy before_action :authorize_admin_issuable!, only: :bulk_update + before_action :set_application_context!, only: :show end def show @@ -226,6 +227,10 @@ module IssuableActions render_404 unless can?(current_user, :"update_#{resource_name}", issuable) end + def set_application_context! + # no-op. The logic is defined in EE module. + end + def bulk_update_params clean_bulk_update_params( params.require(:update).permit(bulk_update_permitted_keys) diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb index 6fd9eae9edb..66a358963e2 100644 --- a/app/controllers/projects/merge_requests/conflicts_controller.rb +++ b/app/controllers/projects/merge_requests/conflicts_controller.rb @@ -75,6 +75,8 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap private + alias_method :issuable, :merge_request + def authorize_can_resolve_conflicts! @conflicts_list = ::MergeRequests::Conflicts::ListService.new(@merge_request) diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 01030690daf..ff5e4248d98 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -5,14 +5,6 @@ module NamespacesHelper params.dig(:project, :namespace_id) || params[:namespace_id] end - def namespace_icon(namespace, size = 40) - if namespace.is_a?(Group) - group_icon_url(namespace) - else - avatar_icon_for_user(namespace.owner, size) - end - end - def cascading_namespace_settings_popover_data(attribute, group, settings_path_helper) locked_by_ancestor = group.namespace_settings.public_send("#{attribute}_locked_by_ancestor?") # rubocop:disable GitlabSecurity/PublicSend diff --git a/app/models/concerns/vulnerability_finding_signature_helpers.rb b/app/models/concerns/vulnerability_finding_signature_helpers.rb index 71a12b4077b..a225625815b 100644 --- a/app/models/concerns/vulnerability_finding_signature_helpers.rb +++ b/app/models/concerns/vulnerability_finding_signature_helpers.rb @@ -2,12 +2,17 @@ module VulnerabilityFindingSignatureHelpers extend ActiveSupport::Concern + # If the location object describes a physical location within a file # (filename + line numbers), the 'location' algorithm_type should be used # If the location object describes arbitrary data, then the 'hash' # algorithm_type should be used. - - ALGORITHM_TYPES = { hash: 1, location: 2, scope_offset: 3 }.with_indifferent_access.freeze + ALGORITHM_TYPES = { + hash: 1, + location: 2, + scope_offset: 3, + scope_offset_compressed: 4 + }.with_indifferent_access.freeze class_methods do def priority(algorithm_type) diff --git a/app/models/user.rb b/app/models/user.rb index a8ce70e6ebf..adbbdf9b0ae 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2067,6 +2067,13 @@ class User < ApplicationRecord super end + # override, from Devise + def unlock_access!(unlocked_by: self) + audit_unlock_access(author: unlocked_by) + + super() + end + # Determine the maximum access level for a group of projects in bulk. # # Returns a Hash mapping project ID -> maximum access level. @@ -2593,6 +2600,9 @@ class User < ApplicationRecord # method overriden in EE def audit_lock_access; end + + # method overriden in EE + def audit_unlock_access(author: self); end end User.prepend_mod_with('User') diff --git a/app/views/shared/_service_ping_consent.html.haml b/app/views/shared/_service_ping_consent.html.haml index 108d846e3ee..e0313710736 100644 --- a/app/views/shared/_service_ping_consent.html.haml +++ b/app/views/shared/_service_ping_consent.html.haml @@ -1,11 +1,14 @@ - if session[:ask_for_usage_stats_consent] = render Pajamas::AlertComponent.new(alert_options: { class: 'service-ping-consent-message' }) do |c| - c.with_body do - - docs_link = link_to _('collect usage information'), help_page_path('user/admin_area/settings/usage_statistics.md'), class: 'gl-link' - - settings_link = link_to _('your settings'), metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), class: 'gl-link' - = s_('To help improve GitLab, we would like to periodically %{docs_link}. This can be changed at any time in %{settings_link}.').html_safe % { docs_link: docs_link, settings_link: settings_link } + - docs_link = link_to '', help_page_path('user/admin_area/settings/usage_statistics.md'), class: 'gl-link' + - settings_link = link_to '', metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), class: 'gl-link' + = safe_format s_('ServicePing|To help improve GitLab, we would like to periodically %{link_start}collect usage information%{link_end}.'), tag_pair(docs_link, :link_start, :link_end) + = safe_format s_('ServicePing|This can be changed at any time in %{link_start}your settings%{link_end}.'), tag_pair(settings_link, :link_start, :link_end) - c.with_actions do - send_service_data_path = admin_application_settings_path(application_setting: { version_check_enabled: 1, usage_ping_enabled: 1 }) - not_now_path = admin_application_settings_path(application_setting: { version_check_enabled: 0, usage_ping_enabled: 0 }) - = link_to _("Send service data"), send_service_data_path, 'data-url' => admin_application_settings_path, method: :put, 'data-check-enabled': true, 'data-service-ping-enabled': true, class: 'js-service-ping-consent-action alert-link btn gl-button btn-confirm' - = link_to _("Don't send service data"), not_now_path, 'data-url' => admin_application_settings_path, method: :put, 'data-check-enabled': false, 'data-service-ping-enabled': false, class: 'js-service-ping-consent-action alert-link btn gl-button btn-default gl-ml-3' + = render Pajamas::ButtonComponent.new(href: send_service_data_path, method: :put, variant: :confirm, button_options: { 'data-url' => admin_application_settings_path, 'data-check-enabled': true, 'data-service-ping-enabled': true, class: 'js-service-ping-consent-action alert-link' }) do + = _('Send service data') + = render Pajamas::ButtonComponent.new(href: not_now_path, method: :put, button_options: { 'data-url' => admin_application_settings_path, 'data-check-enabled': false, 'data-service-ping-enabled': false, class: 'js-service-ping-consent-action alert-link gl-ml-3' }) do + = _("Don't send service data") diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md index a80d3421d34..6f91a6aa4d1 100644 --- a/doc/administration/audit_events.md +++ b/doc/administration/audit_events.md @@ -365,6 +365,7 @@ GitLab generates audit events when a cluster agent token is created or revoked. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/238177) in GitLab 15.1, audit events when a user's two-factor authentication is disabled. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124169) in GitLab 16.2, audit events when a user's access is locked. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124973) in GitLab 16.2, audit events when a user's access is unlocked. The following user actions on a GitLab instance generate instance audit events: @@ -378,6 +379,7 @@ The following user actions on a GitLab instance generate instance audit events: - A user's personal access token was successfully or unsuccessfully created or revoked. - A user's two-factor authentication was disabled. - A user's access is locked. +- A user's access is unlocked. #### User management diff --git a/doc/administration/operations/filesystem_benchmarking.md b/doc/administration/operations/filesystem_benchmarking.md index cd4ab1a9cf8..bd37bd4b1a8 100644 --- a/doc/administration/operations/filesystem_benchmarking.md +++ b/doc/administration/operations/filesystem_benchmarking.md @@ -11,7 +11,7 @@ especially for actions that read or write to Git repositories. This information helps benchmark file system performance against known good and bad real-world systems. -Normally when talking about file system performance the biggest concern is +When talking about file system performance the biggest concern is with Network File Systems (NFS). However, even some local disks can have slow I/O. The information on this page can be used for either scenario. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 69f7d3f0ff5..c0dcdb01d48 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1045,6 +1045,7 @@ Input type: `AdminSidekiqQueuesDeleteJobsInput` | Name | Type | Description | | ---- | ---- | ----------- | +| <a id="mutationadminsidekiqqueuesdeletejobsairesource"></a>`aiResource` | [`String`](#string) | Delete jobs matching ai_resource in the context metadata. | | <a id="mutationadminsidekiqqueuesdeletejobsartifactsize"></a>`artifactSize` | [`String`](#string) | Delete jobs matching artifact_size in the context metadata. | | <a id="mutationadminsidekiqqueuesdeletejobsartifactusedcdn"></a>`artifactUsedCdn` | [`String`](#string) | Delete jobs matching artifact_used_cdn in the context metadata. | | <a id="mutationadminsidekiqqueuesdeletejobsartifactsdependenciescount"></a>`artifactsDependenciesCount` | [`String`](#string) | Delete jobs matching artifacts_dependencies_count in the context metadata. | diff --git a/doc/api/packages/pypi.md b/doc/api/packages/pypi.md index 4e7c59adf3a..1b155feb8b5 100644 --- a/doc/api/packages/pypi.md +++ b/doc/api/packages/pypi.md @@ -28,8 +28,8 @@ is recommended when [FIPS mode](../../development/fips_compliance.md) is enabled > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225545) in GitLab 13.12. -Download a PyPI package file. The [simple API](#group-level-simple-api-entry-point) -normally supplies this URL. +Download a PyPI package file. The [simple API](#group-level-simple-api-entry-point) +usually supplies this URL. ```plaintext GET groups/:id/-/packages/pypi/files/:sha256/:file_identifier @@ -142,7 +142,7 @@ This writes the downloaded file to `simple.html` in the current directory. > Introduced in GitLab 12.10. Download a PyPI package file. The [simple API](#project-level-simple-api-entry-point) -normally supplies this URL. +usually supplies this URL. ```plaintext GET projects/:id/packages/pypi/files/:sha256/:file_identifier diff --git a/doc/architecture/blueprints/remote_development/index.md b/doc/architecture/blueprints/remote_development/index.md index 16b71840f9e..ce55f23f828 100644 --- a/doc/architecture/blueprints/remote_development/index.md +++ b/doc/architecture/blueprints/remote_development/index.md @@ -215,7 +215,7 @@ end note note top of "Load Balancer IP" For local development, it includes all local loopback interfaces - e.g. 127.0.0.1, 172.16.123.1, 192.168.0.1, etc. + for example, 127.0.0.1, 172.16.123.1, 192.168.0.1, etc. end note @enduml @@ -439,7 +439,7 @@ Stopped -up-> Starting : status=Starting Terminated: Workspace has been deleted -Failed: Workspace is not ready due to\nvarious reasons(e.g. crashing container) +Failed: Workspace is not ready due to\nvarious reasons(for example, crashing container) Failed -up-> Starting : status=Starting\n(container\nnot crashing) Failed -right-> Stopped : status=Stopped Failed -down-> Terminated : status=Terminated diff --git a/doc/architecture/blueprints/runner_scaling/index.md b/doc/architecture/blueprints/runner_scaling/index.md index de1203843aa..a6df58aa405 100644 --- a/doc/architecture/blueprints/runner_scaling/index.md +++ b/doc/architecture/blueprints/runner_scaling/index.md @@ -232,12 +232,12 @@ coupled in the current implementation so we will break them out here to consider them each separately. - **Virtual Machine (VM) shape**. The underlying provider of a VM requires configuration to - know what kind of machine to create. E.g. Cores, memory, failure domain, + know what kind of machine to create. For example, Cores, memory, failure domain, etc... This information is very provider specific. - **VM lifecycle management**. Multiple machines will be created and a system must keep track of which machines belong to this executor. Typically a cloud provider will have a way to manage a set of homogeneous machines. - E.g. GCE Instance Group. The basic operations are increase, decrease and + For example, GCE Instance Group. The basic operations are increase, decrease and usually delete a specific machine. - **VM autoscaling**. In addition to low-level lifecycle management, job-aware capacity decisions must be made to the set of machines to provide @@ -255,7 +255,7 @@ See also Glossary below. #### Current state The current architecture has several points of coupling between concerns. -Coupling reduces opportunities for abstraction (e.g. community supported +Coupling reduces opportunities for abstraction (for example, community supported plugins) and increases complexity, making the code harder to understand, test, maintain and extend. diff --git a/doc/architecture/blueprints/runner_tokens/index.md b/doc/architecture/blueprints/runner_tokens/index.md index c83586f851a..d514f5e8f0b 100644 --- a/doc/architecture/blueprints/runner_tokens/index.md +++ b/doc/architecture/blueprints/runner_tokens/index.md @@ -238,7 +238,7 @@ The new workflow looks as follows: 1. Creates a new runner in the `ci_runners` table (and corresponding `glrt-` prefixed authentication token); 1. Presents the user with instructions on how to configure this new runner on a machine, - with possibilities for different supported deployment scenarios (e.g. shell, `docker-compose`, Helm chart, etc.) + with possibilities for different supported deployment scenarios (for example, shell, `docker-compose`, Helm chart, etc.) This information contains a token which is available to the user only once, and the UI makes it clear to the user that the value shall not be shown again, as registering the same runner multiple times is discouraged (though not impossible). diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md index 33d1cf579c0..ee8b6bb30b6 100644 --- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md +++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md @@ -31,7 +31,7 @@ to write such end-to-end tests, and how to set up GitLab CI/CD to automatically against your new code, on a branch-by-branch basis. For the scope of this article, we will walk you through the process of setting up GitLab CI/CD for end-to-end testing JavaScript-based applications with WebdriverIO, but the general strategy should carry over to other languages. -We assume you are familiar with GitLab, [GitLab CI/CD](../../index.md), [Review Apps](../../review_apps/index.md), and running your app locally, e.g., on `localhost:8000`. +We assume you are familiar with GitLab, [GitLab CI/CD](../../index.md), [Review Apps](../../review_apps/index.md), and running your app locally, for example, on `localhost:8000`. ## What to test @@ -45,7 +45,7 @@ infrastructure is up and running, and that your units of code work well together ## Selenium and WebdriverIO -[Selenium](https://www.selenium.dev/) is a piece of software that can control web browsers, e.g., to make them +[Selenium](https://www.selenium.dev/) is a piece of software that can control web browsers, for example, to make them visit a specific URL or interact with elements on the page. It can be programmatically controlled from a variety of programming languages. In this article we're going to be using the [WebdriverIO](http://v4.webdriver.io/) JavaScript bindings, but the general concept should carry over @@ -115,7 +115,7 @@ easiest way to get started is to start with provides an overview of all available options. The two options that are going to be most relevant now are the `specs` option, which is an array of paths to your tests, and the `baseUrl` option, which points to where your app is running. And finally, we will need to tell WebdriverIO in which browsers we would like to run our -tests. This can be configured through the `capabilities` option, which is an array of browser names (e.g. +tests. This can be configured through the `capabilities` option, which is an array of browser names (for example, `firefox` or `chrome`). It is recommended to install [selenium-assistant](https://googlechromelabs.github.io/selenium-assistant/) to detect all installed browsers: @@ -130,7 +130,7 @@ But of course, a simple configuration of `config.capabilities = ['firefox']` wou If you've installed WebdriverIO as a dependency (`npm install --save-dev webdriverio`), you can add a line to the `scripts` property in your -`package.json` that runs `wdio` with the path to your configuration file as value, e.g.: +`package.json` that runs `wdio` with the path to your configuration file as value, for example: ```javascript "confidence-check": "wdio wdio.conf.js", diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 49a3e8fd70e..151b245b0c1 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -4790,6 +4790,6 @@ important to describe those, too. Think of things that may go wrong and include This is important to minimize requests for support, and to avoid doc comments with questions that you know someone might ask. -Each scenario can be a third-level heading, e.g. `### Getting error message X`. +Each scenario can be a third-level heading, for example, `### Getting error message X`. If you have none to add when creating a doc, leave this section in place but commented out to help encourage others to add to it in the future. --> diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md index ea3377ef9d8..2ed4d934022 100644 --- a/doc/development/api_styleguide.md +++ b/doc/development/api_styleguide.md @@ -179,7 +179,7 @@ request that has an optional parameter: optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The user ids for this rule' ``` -Normally, a request to PUT `/test?user_ids` would cause Grape to pass +Usually, a request to PUT `/test?user_ids` would cause Grape to pass `params` of `{ user_ids: nil }`. This may introduce errors with endpoints that expect a blank array and diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 133cf8a2998..25f471dc9d2 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -1011,7 +1011,7 @@ Puma. All these components should run as different system users to GitLab (for example, `postgres`, `redis`, and `www-data`, instead of `git`). As the `git` user it starts Sidekiq and Puma (a simple Ruby HTTP server -running on port `8080` by default). Under the GitLab user there are normally 4 +running on port `8080` by default). Under the GitLab user there are usually 4 processes: `puma master` (1 process), `puma cluster worker` (2 processes), `sidekiq` (1 process). @@ -1067,7 +1067,7 @@ Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [v GitLab (includes Puma and Sidekiq logs): -- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `puma.stdout.log`, `git_json.log` and `puma.stderr.log` normally. +- `/home/git/gitlab/log/` usually contains `application.log`, `production.log`, `sidekiq.log`, `puma.stdout.log`, `git_json.log` and `puma.stderr.log`. GitLab Shell: diff --git a/doc/development/cicd/pipeline_wizard.md b/doc/development/cicd/pipeline_wizard.md index 213a9fe1762..f322ec2e2bf 100644 --- a/doc/development/cicd/pipeline_wizard.md +++ b/doc/development/cicd/pipeline_wizard.md @@ -143,7 +143,7 @@ user to the pipeline, for example. ### Template file location -Template files are normally stored as YAML files in `~/pipeline_wizard/templates/`. +Template files are usually stored as YAML files in `~/pipeline_wizard/templates/`. The `PipelineWizard` component expects the `template` property as an un-parsed `String`, and Webpack is configured to load `.yml` files from the above folder as strings. diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md index d24875e559a..651c7214275 100644 --- a/doc/development/contributing/style_guides.md +++ b/doc/development/contributing/style_guides.md @@ -31,7 +31,7 @@ We were using Overcommit prior to Lefthook, so you may want to uninstall it firs ### Install Lefthook 1. You can install lefthook in [different ways](https://github.com/evilmartians/lefthook/blob/master/docs/install.md#install-lefthook). - If you do not choose to install it globally (e.g. via Homebrew or package managers), and only want to use it for the GitLab project, + If you do not choose to install it globally (for example, via Homebrew or package managers), and only want to use it for the GitLab project, you can install the Ruby gem via: ```shell diff --git a/doc/development/database/insert_into_tables_in_batches.md b/doc/development/database/insert_into_tables_in_batches.md index 78aa793f28b..547ca25b264 100644 --- a/doc/development/database/insert_into_tables_in_batches.md +++ b/doc/development/database/insert_into_tables_in_batches.md @@ -141,7 +141,7 @@ the key differences between these classes are listed in the table below. | `insert_all!` | Attribute hashes | No | No | Yes | Yes | To summarize, `BulkInsertSafe` moves bulk inserts closer to how ActiveRecord objects -and inserts would normally behave. However, if all you need is to insert raw data in bulk, then +and inserts would usually behave. However, if all you need is to insert raw data in bulk, then `insert_all` is more efficient. ## Insert `has_many` associations in bulk diff --git a/doc/development/database/migrations_for_multiple_databases.md b/doc/development/database/migrations_for_multiple_databases.md index b903c56651d..ae9348a2090 100644 --- a/doc/development/database/migrations_for_multiple_databases.md +++ b/doc/development/database/migrations_for_multiple_databases.md @@ -32,8 +32,8 @@ Migrations cannot mix **DDL** and **DML** changes as the application requires th The DDL migrations are all migrations that: 1. Create or drop a table (for example, `create_table`). -1. Add or remove an index (for example, `add_index`, `add_index_concurrently`). -1. Add or remove a foreign key (for example `add_foreign_key`, `add_foreign_key_concurrently`). +1. Add or remove an index (for example, `add_index`, `add_concurrent_index`). +1. Add or remove a foreign key (for example `add_foreign_key`, `add_concurrent_foreign_key`). 1. Add or remove a column with or without a default value (for example, `add_column`). 1. Create or drop trigger functions (for example, `create_trigger_function`). 1. Attach or detach triggers from tables (for example, `track_record_deletions`, `untrack_record_deletions`). diff --git a/doc/development/database/post_deployment_migrations.md b/doc/development/database/post_deployment_migrations.md index 0d0047531f9..8ee9a4ae099 100644 --- a/doc/development/database/post_deployment_migrations.md +++ b/doc/development/database/post_deployment_migrations.md @@ -33,7 +33,7 @@ release managers through the Say you're using Chef for deploying new versions of GitLab and you'd like to run post deployment migrations after deploying a new version. Let's assume you -normally use the command `chef-client` to do so. To make use of this feature +usually use the command `chef-client` to do so. To make use of this feature you'd have to run this command as follows: ```shell @@ -63,7 +63,7 @@ behave exactly like regular Rails migrations. Post deployment migrations can be used to perform migrations that mutate state that an existing version of GitLab depends on. For example, say you want to remove a column from a table. This requires downtime as a GitLab instance -depends on this column being present while it's running. Normally you'd follow +depends on this column being present while it's running. Usually you'd follow these steps in such a case: 1. Stop the GitLab instance diff --git a/doc/development/database_review.md b/doc/development/database_review.md index 76e9add215b..7b121af13cc 100644 --- a/doc/development/database_review.md +++ b/doc/development/database_review.md @@ -244,7 +244,7 @@ Include in the MR description: - Manually trigger the [database testing](database/database_migration_pipeline.md) job (`db:gitlabcom-database-testing`) in the `test` stage. - If a single `update` is below than `1s` the query can be placed directly in a regular migration (inside `db/migrate`). - - Background migrations are normally used, but not limited to: + - Background migrations are usually used, but not limited to: - Migrating data in larger tables. - Making numerous SQL queries per record in a dataset. - Review queries (for example, make sure batch sizes are fine) diff --git a/doc/development/distributed_tracing.md b/doc/development/distributed_tracing.md index 823a71eb130..da6af8b95ef 100644 --- a/doc/development/distributed_tracing.md +++ b/doc/development/distributed_tracing.md @@ -40,7 +40,7 @@ better understand the end-to-end path of a request through the system. When a re process boundaries, the correlation ID is injected into the outgoing request. This enables the propagation of the correlation ID to each downstream subsystem. -Correlation IDs are normally generated in the Rails application in response to +Correlation IDs are usually generated in the Rails application in response to certain web requests. Some user facing systems don't generate correlation IDs in response to user requests (for example, Git pushes over SSH). @@ -139,8 +139,8 @@ This can be shown by typing `p` `b` in the browser window. Once the performance bar is enabled, select **Trace** in the performance bar to go to the Jaeger UI. -The Jaeger search UI returns a query for the `Correlation-ID` of the current request. Normally, -this search should return a single trace result. Selecting this result shows the detail of the +The Jaeger search UI returns a query for the `Correlation-ID` of the current request. +This search should return a single trace result. Selecting this result shows the detail of the trace in a hierarchical time-line. ![Jaeger Search UI](img/distributed_tracing_jaeger_ui.png) diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 761dde839c1..3f26a5a7f4e 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -223,7 +223,7 @@ merge it as early as possible. ### Linking to `/help` When you're building a new feature, you may need to link to the documentation -from the GitLab application. This is normally done in files inside the +from the GitLab application. This is usually done in files inside the `app/views/` directory, with the help of the `help_page_path` helper method. The `help_page_path` contains the path to the document you want to link to, diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index aeb9739ecb3..9219fcd6710 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -222,7 +222,7 @@ This also applies to views. #### Testing EE-only backend features -To test an EE class that doesn't exist in CE, create the spec file as you normally +To test an EE class that doesn't exist in CE, create the spec file as you usually would in the `ee/spec` directory, but without the second `ee/` subdirectory. For example, a class `ee/app/models/vulnerability.rb` would have its tests in `ee/spec/models/vulnerability_spec.rb`. @@ -303,7 +303,7 @@ This is also not just applied to models. Here's a list of other examples: #### Testing EE features based on CE features To test an `EE` namespaced module that extends a CE class with EE features, -create the spec file as you normally would in the `ee/spec` directory, including the second `ee/` subdirectory. +create the spec file as you usually would in the `ee/spec` directory, including the second `ee/` subdirectory. For example, an extension `ee/app/models/ee/user.rb` would have its tests in `ee/spec/models/ee/user_spec.rb`. In the `RSpec.describe` call, use the CE class name where the EE module would be used. @@ -713,7 +713,7 @@ end ### Code in `lib/` Place EE-specific logic in the top-level `EE` module namespace. Namespace the -class beneath the `EE` module just as you would normally. +class beneath the `EE` module as you usually would. For example, if CE has LDAP classes in `lib/gitlab/ldap/` then you would place EE-specific LDAP classes in `ee/lib/ee/gitlab/ldap`. @@ -870,7 +870,7 @@ end #### EE-specific behavior -Sometimes we need EE-specific behavior in some of the APIs. Normally we could +Sometimes we need EE-specific behavior in some of the APIs. Usually we could use EE methods to override CE methods, however API routes are not methods and therefore cannot be overridden. We need to extract them into a standalone method, or introduce some "hooks" where we could inject behavior in the CE diff --git a/doc/development/experiment_guide/implementing_experiments.md b/doc/development/experiment_guide/implementing_experiments.md index 5bce9f1fab5..6fe58a1da54 100644 --- a/doc/development/experiment_guide/implementing_experiments.md +++ b/doc/development/experiment_guide/implementing_experiments.md @@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w [Examples](https://gitlab.com/gitlab-org/growth/growth/-/wikis/GLEX-Framework-code-examples) Start by generating a feature flag using the `bin/feature-flag` command as you -normally would for a development feature flag, making sure to use `experiment` for +usually would for a development feature flag, making sure to use `experiment` for the type. For the sake of documentation let's name our feature flag (and experiment) `pill_color`. @@ -280,7 +280,7 @@ about contexts now. We can assume we run the experiment in one or a few places, but track events potentially in many places. The tracking call remains the same, with -the arguments you would normally use when +the arguments you would usually use when [tracking events using snowplow](../snowplow/index.md). The easiest example of tracking an event in Ruby would be: diff --git a/doc/development/fe_guide/logging.md b/doc/development/fe_guide/logging.md index b2b0771d9e5..750bf95e8b2 100644 --- a/doc/development/fe_guide/logging.md +++ b/doc/development/fe_guide/logging.md @@ -36,7 +36,7 @@ try { } catch (e) { if (e instanceof FooSyntaxError) { // To handle a `FooSyntaxError`, we just need to instruct the user to change their input. - // This isn't unexpected, and is part of normal operations. + // This isn't unexpected, and is part of standard operations. setUserMessage(`Try writing better code. ${e.message}`); } else { // We're not sure what `e` is, so something unexpected and bad happened... diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md index 52278c94e9f..e096d25f2d9 100644 --- a/doc/development/fe_guide/vuex.md +++ b/doc/development/fe_guide/vuex.md @@ -284,7 +284,7 @@ To set this initial state, pass it as a parameter to your store's creation function when mounting your Vue component: ```javascript -// in the Vue app's initialization script (e.g. mount_show.js) +// in the Vue app's initialization script (for example, mount_show.js) import Vue from 'vue'; import Vuex from 'vuex'; diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md index 5ab31b29704..d9eb29a7b7f 100644 --- a/doc/development/feature_flags/index.md +++ b/doc/development/feature_flags/index.md @@ -149,7 +149,7 @@ created using the [Experiment Tracking template](https://gitlab.com/gitlab-org/g `worker` feature flags are used for controlling Sidekiq workers behavior, such as deferring Sidekiq jobs. `worker` feature flags likely do not have any YAML definition as the name could be dynamically generated using -the worker name itself, e.g. `run_sidekiq_jobs_AuthorizedProjectsWorker`. Some examples for using `worker` type feature +the worker name itself, for example, `run_sidekiq_jobs_AuthorizedProjectsWorker`. Some examples for using `worker` type feature flags can be found in [deferring Sidekiq jobs](#deferring-sidekiq-jobs). ## Feature flag definition and validation @@ -348,7 +348,7 @@ Use the `push_frontend_feature_flag` method which is available to all controller ```ruby before_action do - # Prefer to scope it per project or user e.g. + # Prefer to scope it per project or user, for example push_frontend_feature_flag(:vim_bindings, project) end diff --git a/doc/development/gitlab_flavored_markdown/specification_guide/index.md b/doc/development/gitlab_flavored_markdown/specification_guide/index.md index ba37c858b63..562fd445ab3 100644 --- a/doc/development/gitlab_flavored_markdown/specification_guide/index.md +++ b/doc/development/gitlab_flavored_markdown/specification_guide/index.md @@ -133,10 +133,10 @@ The following taxonomy chart shows the taxonomy and terminology of the various s ```mermaid graph TD -CM[CommonMark - spec.txt - e.g. headings] --- GFMS[GFM Specification - spec.txt - e.g. strikethrough extension] -GFMS --- GLFM[GLFM Specification - e.g. color chips] -GFMS --- GFMI[GFM internal extensions - e.g. GitHub-specific references] -GLFM --- GLFS[GLFM internal extensions - e.g. GitLab-specific references] +CM[CommonMark - spec.txt - for example, headings] --- GFMS[GFM Specification - spec.txt - for example, strikethrough extension] +GFMS --- GLFM[GLFM Specification - for example, color chips] +GFMS --- GFMI[GFM internal extensions - for example, GitHub-specific references] +GLFM --- GLFS[GLFM internal extensions - for example, GitLab-specific references] ``` ##### Official specifications diff --git a/doc/development/import_export.md b/doc/development/import_export.md index b8493ef7a6e..9a7944ae308 100644 --- a/doc/development/import_export.md +++ b/doc/development/import_export.md @@ -46,8 +46,9 @@ The `AttributeConfigurationSpec` checks and confirms the addition of new columns <<-MSG It looks like #{relation_class}, which is exported using the project Import/Export, has new attributes: - Please add the attribute(s) to SAFE_MODEL_ATTRIBUTES if you consider this can be exported. - Otherwise, please blacklist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its correspondent + Please add the attribute(s) to SAFE_MODEL_ATTRIBUTES if they can be exported. + + Please denylist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its corresponding model in the +excluded_attributes+ section. SAFE_MODEL_ATTRIBUTES: #{File.expand_path(safe_attributes_file)} diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 55884152075..8cbf18ce9f2 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -229,13 +229,13 @@ end Migrations like this are inherently risky and [additional actions](database_review.md#preparation-when-adding-data-migrations) are required when preparing the migration for review. -## Atomicity +## Atomicity and transaction -By default, migrations are single transaction. That is, a transaction is opened +By default, migrations are a single transaction: it's opened at the beginning of the migration, and committed after all steps are processed. Running migrations in a single transaction makes sure that if one of the steps fails, -none of the steps are executed, leaving the database in valid state. +none of the steps are executed, leaving the database in a valid state. Therefore, either: - Put all migrations in one single-transaction migration. @@ -243,11 +243,141 @@ Therefore, either: for the steps that cannot be done in a single transaction. For example, if you create an empty table and need to build an index for it, -it is recommended to use a regular single-transaction migration and the default +you should use a regular single-transaction migration and the default rails schema statement: [`add_index`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_index). -This is a blocking operation, but it doesn't cause problems because the table is not yet used, +This operation is a blocking operation, but it doesn't cause problems because the table is not yet used, and therefore it does not have any records yet. +NOTE: +Subtransactions are [disallowed](https://about.gitlab.com/blog/2021/09/29/why-we-spent-the-last-month-eliminating-postgresql-subtransactions/) in general. +Use multiple, separate transactions +if needed as described in [Heavy operations in a single transaction](#heavy-operations-in-a-single-transaction). + +### Heavy operations in a single transaction + +When using a single-transaction migration, a transaction holds a database connection +for the duration of the migration, so you must make sure the actions in the migration +do not take too much time. +In general, transactions must [execute quickly](database/transaction_guidelines.md#transaction-speed). +To that end, observe [the maximum query time limit](database/query_performance.md#timing-guidelines-for-queries) +for each query run in the migration. + +If your single-transaction migration takes long to finish, you have several options. +In all cases, remember to select the appropriate migration type +depending on [how long a migration takes](#how-long-a-migration-should-take) + +- Split the migration into **multiple single-transaction migrations**. + +- Use **multiple transactions** by [using `disable_ddl_transaction!`](#disable-transaction-wrapped-migration). + +- Keep using a single-transaction migration after **adjusting statement and lock timeout settings**. + If your heavy workload must use the guarantees of a transaction, + you should check your migration can execute without hitting the timeout limits. + The same advice applies to both single-transaction migrations and individual transactions. + + - Statement timeout: the statement timeout is configured to be `15s` for GitLab.com's production database + but creating an index often takes more than 15 seconds. + When you use the existing helpers including `add_concurrent_index`, + they automatically turn off the statement timeout as needed. + In rare cases, you might need to set the timeout limit yourself by [using `disable_statement_timeout`](#temporarily-turn-off-the-statement-timeout-limit). + - Lock timeout: if your migration must execute as a transaction but can possibly time out while + acquiring a lock, [use `enable_lock_retries!`](#usage-with-transactional-migrations). + +NOTE: +To run migrations, we directly connect to the primary database, bypassing PgBouncer +to control settings like `statement_timeout` and `lock_wait_timeout`. + +#### Temporarily turn off the statement timeout limit + +The migration helper `disable_statement_timeout` enables you to +temporarily set the statement timeout to `0` per transaction or per connection. + +- You use the per-connection option when your statement does not support + running inside an explicit transaction, like `CREATE INDEX CONCURRENTLY`. + +- If your statement does support an explicit transaction block, + like `ALTER TABLE ... VALIDATE CONSTRAINT`, + the per-transaction option should be used. + +Using `disable_statement_timeout` is rarely needed, because +the most migration helpers already use them internally when needed. +For example, creating an index usually takes more than 15 seconds, +which is the default statement timeout configured for GitLab.com's production database. +The helper `add_concurrent_index` creates an index inside the block +passed to `disable_statement_timeout` to disable the statement timeout per connection. + +If you are writing raw SQL statements in a migration, +you may need to manually use `disable_statement_timeout`. +Consult the database reviewers and maintainers when you do. + +### Disable transaction-wrapped migration + +You can opt out of running your migration as a single transaction by using +`disable_ddl_transaction!`, an ActiveRecord method. +The method might be called in other database systems, with different results. +At GitLab we exclusively use PostgreSQL. +You should always read `disable_ddl_transaction!` as meaning: + +"Do not execute this migration in a single PostgreSQL transaction. I'll open PostgreSQL transaction(s) only _when_ and _if_ I need them." + +NOTE: +Even if you don't use an explicit PostgreSQL transaction `.transaction` (or `BEGIN; COMMIT;`), +every SQL statement is still executed as a transaction. +See [the PostgreSQL documentation on transactions](https://www.postgresql.org/docs/current/tutorial-transactions.html). + +NOTE: +In GitLab, we've sometimes referred to +the migrations that used `disable_ddl_transaction!` as non-transactional migrations. +It just meant the migrations were not executed as _single_ transactions. + +When should you use `disable_ddl_transaction!`? In most cases, +the existing RuboCop rules or migration helpers can detect if you should be +using `disable_ddl_transaction!`. +Skip `disable_ddl_transaction!` if you are unsure whether to use it or not in your migration, +and let the RuboCop rules and database reviews guide you. + +Use `disable_ddl_transaction!` when PostgreSQL requires an operation to be executed outside an explicit transaction. + +- The most prominent example of such operation is the command `CREATE INDEX CONCURRENTLY`. + PostgreSQL allows the blocking version (`CREATE INDEX`) to be run inside a transaction. + Unlike `CREATE INDEX`, `CREATE INDEX CONCURRENTLY` must be performed outside a transaction. + Therefore, even though a migration may run just one statement `CREATE INDEX CONCURRENTLY`, + you should disable `disable_ddl_transaction!`. + It's also the reason why the use of the helper `add_concurrent_index` requires `disable_ddl_transaction!` + `CREATE INDEX CONCURRENTLY` is more of the exception than the rule. + +Use `disable_ddl_transaction!` when you need to run multiple transactions in a migration for any reason. +Most of the time you would be using multiple transactions to avoid [running one slow transaction](#heavy-operations-in-a-single-transaction). + +- For example, when you insert, update, or delete (DML) a large amount of data, + you should [perform them in batches](database/iterating_tables_in_batches.md#eachbatch-in-data-migrations). + Should you need to group operations for each batch, + you can explicitly open a transaction block when processing a batch. + Consider using a [batched background migration](database/batched_background_migrations.md) for + any reasonably large workload. + +Use `disable_ddl_transaction!` when migration helpers require them. +Various migration helpers need to run with `disable_ddl_transaction!` +because they require a precise control on when and how to open transactions. + +- A foreign key _can_ be added inside a transaction, unlike `CREATE INDEX CONCURRENTLY`. + However, PostgreSQL does not provide an option similar to `CREATE INDEX CONCURRENTLY`. + The helper [`add_concurrent_foreign_key`](database/foreign_keys.md#adding-foreign-keys-in-migrations) + instead opens its own transactions to lock the source and target table + in a manner that minimizes locking while adding and validating the foreign key. +- As advised earlier, skip `disable_ddl_transaction!` if you are unsure + and see if any RuboCop check is violated. + +Use `disable_ddl_transaction!` when your migration does not actually touch PostgreSQL databases +or does touch _multiple_ PostgreSQL databases. + +- For example, your migration might target a Redis server. As a rule, + you cannot [interact with an external service](database/transaction_guidelines.md#dangerous-example-third-party-api-calls) + inside a PostgreSQL transaction. +- A transaction is used for a single database connection. + If your migrations are targeting multiple databases, such as both `ci` and `main` database, + follow [Migrations for multiple databases](database/migrations_for_multiple_databases.md). + ## Naming conventions Names for database objects (such as tables, indexes, and views) must be lowercase. @@ -290,19 +420,6 @@ minimum acceptable timestamp would be 20230424000000. While the above should be considered a hard rule, it is a best practice to try to keep migration timestamps to within three weeks of the date it is anticipated that the migration will be merged upstream, regardless of how much time has elapsed since the last hard stop. -## Heavy operations in a single transaction - -When using a single-transaction migration, a transaction holds a database connection -for the duration of the migration, so you must make sure the actions in the migration -do not take too much time: GitLab.com's production database has a `15s` timeout, so -in general, the cumulative execution time in a migration should aim to fit comfortably -in that limit. Singular query timings should fit within the [standard limit](database/query_performance.md#timing-guidelines-for-queries) - -In case you need to insert, update, or delete a significant amount of data, you: - -- Must disable the single transaction with `disable_ddl_transaction!`. -- Should consider doing it in a [batched background migration](database/batched_background_migrations.md). - ## Migration helpers and versioning > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/339115) in GitLab 14.3. @@ -610,6 +727,71 @@ like a standard migration invocation. The migration might fail if there is a very long running transaction (40+ minutes) accessing the `users` table. +#### Lock-retry methodology at the SQL level + +In this section, we provide a simplified SQL example that demonstrates the use of `lock_timeout`. +You can follow along by running the given snippets in multiple `psql` sessions. + +When altering a table to add a column, +`AccessExclusiveLock`, which conflicts with most lock types, is required on the table. +If the target table is a very busy one, the transaction adding the column +may fail to acquire `AccessExclusiveLock` in a timely fashion. + +Suppose a transaction is attempting to insert a row into a table: + +```sql +-- Transaction 1 +BEGIN; +INSERT INTO my_notes (id) VALUES (1); +``` + +At this point Transaction 1 acquired `RowExclusiveLock` on `my_notes`. +Transaction 1 could still execute more statements prior to committing or aborting. +There could be other similar, concurrent transactions that touch `my_notes`. + +Suppose a transactional migration is attempting to add a column to the table +without using any lock retry helper: + +```sql +-- Transaction 2 +BEGIN; +ALTER TABLE my_notes ADD COLUMN title text; +``` + +Transaction 2 is now blocked because it cannot acquire +`AccessExclusiveLock` on `my_notes` table +as Transaction 1 is still executing and holding the `RowExclusiveLock` +on `my_notes`. + +A more pernicious effect is blocking the transactions that would +normally not conflict with Transaction 1 because Transaction 2 +is queueing to acquire `AccessExclusiveLock`. +In a normal situation, if another transaction attempted to read from and write +to the same table `my_notes` at the same time as Transaction 1, +the transaction would go through +since the locks needed for reading and writing would not +conflict with `RowExclusiveLock` held by Transaction 1. +However, when the request to acquire `AccessExclusiveLock` is queued, +the subsequent requests for conflicting locks on the table would block although +they could be executed concurrently alongside Transaction 1. + +If we used `with_lock_retries`, Transaction 2 would instead quickly +timeout after failing to acquire the lock within the specified time period +and allow other transactions to proceed: + +```sql +-- Transaction 2 (version with with lock timeout) +BEGIN; +SET LOCAL lock_timeout to '100ms'; -- added by the lock retry helper. +ALTER TABLE my_notes ADD COLUMN title text; +``` + +The lock retry helper would repeatedly try the same transaction +at different time intervals until it succeeded. + +Note that `SET LOCAL` scopes the parameter (`lock_timeout`) change to +the transaction. + ## Removing indexes If the table is not empty when removing an index, make sure to use the method diff --git a/doc/development/spam_protection_and_captcha/web_ui.md b/doc/development/spam_protection_and_captcha/web_ui.md index 0cc17854010..9f92105b18d 100644 --- a/doc/development/spam_protection_and_captcha/web_ui.md +++ b/doc/development/spam_protection_and_captcha/web_ui.md @@ -48,11 +48,11 @@ However, even though the actual handling of the request interception and modal is transparent, without any mandatory changes to the involved JavaScript or Vue components for the form or page, changes in request or error handling may be required. Changes are needed because the existing behavior may not work correctly: for example, if a failed or cancelled -CAPTCHA display interrupts the normal request flow or UI updates. +CAPTCHA display interrupts the standard request flow or UI updates. Careful exploratory testing of all scenarios is important to uncover any potential problems. -This sequence diagram illustrates the normal CAPTCHA flow for JavaScript XHR/Fetch requests +This sequence diagram illustrates the standard CAPTCHA flow for JavaScript XHR/Fetch requests on the frontend: ```mermaid @@ -73,7 +73,7 @@ sequenceDiagram ``` The backend is also cleanly abstracted via mixin modules and helper methods. The three main -changes required to the relevant backend controller actions (normally just `create`/`update`) are: +changes required to the relevant backend controller actions (typically just `create`/`update`) are: 1. Pass `perform_spam_check: true` to the Update Service class constructor. It is set to `true` by default in the Create Service. @@ -86,7 +86,7 @@ changes required to the relevant backend controller actions (normally just `crea 1. Checking if there the model contains an error, and the `needs_recaptcha` flag is true. - If yes: Add the appropriate spam or CAPTCHA fields to the JSON response, and return a `409 - Conflict` HTTP status code. - - If no (if CAPTCHA is disabled or if no spam was detected): The normal request return + - If no (if CAPTCHA is disabled or if no spam was detected): The standard request return logic passed in the block is run. Thanks to the abstractions, it's more straightforward to implement than it is to explain it. diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index b1fde5ed139..2c0b5b13f60 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -540,7 +540,7 @@ describe('when logged in', () => { ### Ensuring that tests are isolated -Tests are normally architected in a pattern which requires a recurring setup of the component under test. This is often achieved by making use of the `beforeEach` hook. +Tests are typically architected in a pattern which requires a recurring setup of the component under test. This is often achieved by making use of the `beforeEach` hook. Example @@ -1225,7 +1225,7 @@ You can download any older version of Firefox from the releases FTP server, <htt 1. Rename the application to something like `Firefox_Old`. 1. Move the application to the `Applications` folder. 1. Open up a terminal and run `/Applications/Firefox_Old.app/Contents/MacOS/firefox-bin -profilemanager` to create a new profile specific to that Firefox version. -1. Once the profile has been created, quit the app, and run it again like normal. You now have a working older Firefox version. +1. Once the profile has been created, quit the app, and run it again like usual. You now have a working older Firefox version. ## Snapshots @@ -1742,7 +1742,7 @@ If you are stubbing an `ee` feature flag, then use: You can run your spec with the prefix `WEBDRIVER_HEADLESS=0` to open an actual browser. However, the specs goes though the commands quickly and leaves you no time to look around. -To avoid this problem, you can write `binding.pry` on the line where you want Capybara to stop execution. You are then inside the browser with normal usage. To understand why you cannot find certain elements, you can: +To avoid this problem, you can write `binding.pry` on the line where you want Capybara to stop execution. You are then inside the browser with standard usage. To understand why you cannot find certain elements, you can: - Select elements. - Use the console and network tab. diff --git a/doc/development/workhorse/channel.md b/doc/development/workhorse/channel.md index f5693b57f7a..2c28cea42a3 100644 --- a/doc/development/workhorse/channel.md +++ b/doc/development/workhorse/channel.md @@ -37,7 +37,7 @@ Sec-WebSocket-Protocol: terminal.gitlab.com ``` At this point, the connection is still HTTP, so this is a request. -The server can send a normal HTTP response, such as `404 Not Found` or +The server can send a standard HTTP response, such as `404 Not Found` or `500 Internal Server Error`. If the server decides to permit the upgrade, it sends a HTTP @@ -116,7 +116,7 @@ contain ANSI terminal control codes, and may be in any encoding. ## Workhorse to GitLab Using the terminal as an example, before upgrading the browser, -Workhorse sends a normal HTTP request to GitLab on a URL like +Workhorse sends a standard HTTP request to GitLab on a URL like `https://gitlab.com/group/project/environments/1/terminal.ws/authorize`. This returns a JSON response containing details of where the terminal can be found, and how to connect it. In particular, diff --git a/doc/install/aws/gitlab_hybrid_on_aws.md b/doc/install/aws/gitlab_hybrid_on_aws.md index 84b5728dbd8..b4d55e30ab1 100644 --- a/doc/install/aws/gitlab_hybrid_on_aws.md +++ b/doc/install/aws/gitlab_hybrid_on_aws.md @@ -186,7 +186,7 @@ If EKS node autoscaling is employed, it is likely that your average loading will - [3K AutoScale from 25% GPT Test Results](https://gitlab.com/guided-explorations/aws/implementation-patterns/gitlab-cloud-native-hybrid-on-eks/-/blob/master/gitlab-alliances-testing/3K/3k-QuickStart-AutoScale-ARM-RDS-Cache_v13-12-3-ee_2021-07-23_194200/3k-QuickStart-AutoScale-ARM-RDS-Cache_v13-12-3-ee_2021-07-23_194200_results.txt) - Elastic Auto Scale GPT Test Results start with an idle scaled cluster and then start the standard GPT test to determine if the EKS Auto Scaler performs well enough to keep up with performance test demands. In general this is substantially harder ramping than the scaling required when the ramping is driven my normal production workloads. + Elastic Auto Scale GPT Test Results start with an idle scaled cluster and then start the standard GPT test to determine if the EKS Auto Scaler performs well enough to keep up with performance test demands. In general this is substantially harder ramping than the scaling required when the ramping is driven by standard production workloads. **Deploy Now** @@ -240,7 +240,7 @@ If EKS node autoscaling is employed, it is likely that your average loading will - [5K AutoScale from 25% GPT Test Results](https://gitlab.com/guided-explorations/aws/implementation-patterns/gitlab-cloud-native-hybrid-on-eks/-/blob/master/gitlab-alliances-testing/5K/5k-QuickStart-AutoScale-From-25Percent-ARM-RDS-Redis_v13-12-3-ee_2021-07-24_102717/5k-QuickStart-AutoScale-From-25Percent-ARM-RDS-Redis_v13-12-3-ee_2021-07-24_102717_results.txt) - Elastic Auto Scale GPT Test Results start with an idle scaled cluster and then start the standard GPT test to determine if the EKS Auto Scaler performs well enough to keep up with performance test demands. In general this is substantially harder ramping than the scaling required when the ramping is driven my normal production workloads. + Elastic Auto Scale GPT Test Results start with an idle scaled cluster and then start the standard GPT test to determine if the EKS Auto Scaler performs well enough to keep up with performance test demands. In general this is substantially harder ramping than the scaling required when the ramping is driven by standard production workloads. **Deploy Now** @@ -294,7 +294,7 @@ If EKS node autoscaling is employed, it is likely that your average loading will - [10K Elastic Auto Scale GPT Test Results](https://gitlab.com/guided-explorations/aws/implementation-patterns/gitlab-cloud-native-hybrid-on-eks/-/blob/master/gitlab-alliances-testing/10K/GL-CloudNative-10k-AutoScaling-Test_v13-12-3-ee_2021-07-09_115139/GL-CloudNative-10k-AutoScaling-Test_v13-12-3-ee_2021-07-09_115139_results.txt) - Elastic Auto Scale GPT Test Results start with an idle scaled cluster and then start the standard GPT test to determine if the EKS Auto Scaler performs well enough to keep up with performance test demands. In general this is substantially harder ramping than the scaling required when the ramping is driven my normal production workloads. + Elastic Auto Scale GPT Test Results start with an idle scaled cluster and then start the standard GPT test to determine if the EKS Auto Scaler performs well enough to keep up with performance test demands. In general this is substantially harder ramping than the scaling required when the ramping is driven by standard production workloads. **Deploy Now** @@ -347,7 +347,7 @@ If EKS node autoscaling is employed, it is likely that your average loading will - [50K Elastic Auto Scale GPT Test Results](https://gitlab.com/guided-explorations/aws/implementation-patterns/gitlab-cloud-native-hybrid-on-eks/-/blob/master/gitlab-alliances-testing/50K/50k-AutoScale-Test_v13-12-3-ee_2021-08-13_192633/50k-AutoScale-Test_v13-12-3-ee_2021-08-13_192633.txt) - Elastic Auto Scale GPT Test Results start with an idle scaled cluster and then start the standard GPT test to determine if the EKS Auto Scaler performs well enough to keep up with performance test demands. In general this is substantially harder ramping than the scaling required when the ramping is driven my normal production workloads. + Elastic Auto Scale GPT Test Results start with an idle scaled cluster and then start the standard GPT test to determine if the EKS Auto Scaler performs well enough to keep up with performance test demands. In general this is substantially harder ramping than the scaling required when the ramping is driven by standard production workloads. **Deploy Now** diff --git a/doc/user/application_security/dast/checks/16.9.md b/doc/user/application_security/dast/checks/16.9.md index b0ba502b578..c63a620794e 100644 --- a/doc/user/application_security/dast/checks/16.9.md +++ b/doc/user/application_security/dast/checks/16.9.md @@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w ## Description A `Content-Security-Policy-Report-Only` (CSPRO) was identified on the target site. CSP-Report-Only headers -aid in determining how to implement a `Content-Security-Policy` that does not disrupt normal use of the target +aid in determining how to implement a `Content-Security-Policy` that does not disrupt use of the target site. ## Remediation diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md index 9436ff317a7..18429d6fe0c 100644 --- a/doc/user/group/epics/manage_epics.md +++ b/doc/user/group/epics/manage_epics.md @@ -161,10 +161,6 @@ Prerequisites: To close an epic, at the top of an epic, select **Close epic**. -<!-- Delete when the `moved_mr_sidebar` feature flag is removed --> -If you don't see this action at the top of an epic, your project or instance might have -enabled a feature flag for [moved actions](../../project/merge_requests/index.md#move-sidebar-actions) - You can also use the `/close` [quick action](../../project/quick_actions.md). ## Reopen a closed epic diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md index a6472cf1290..97e7b4a04af 100644 --- a/doc/user/project/issues/managing_issues.md +++ b/doc/user/project/issues/managing_issues.md @@ -215,10 +215,6 @@ To close an issue, you can either: 1. Select **Plan > Issues**, then select your issue to view it. 1. At the top of the issue, select **Close issue**. -<!-- Delete when the `moved_mr_sidebar` feature flag is removed --> -If you don't see this action at the top of an issue, your project or instance might have -enabled a feature flag for [moved actions](../merge_requests/index.md#move-sidebar-actions). - ### Reopen a closed issue Prerequisites: diff --git a/doc/user/project/repository/push_rules.md b/doc/user/project/repository/push_rules.md index 30871f08c5e..81896d64815 100644 --- a/doc/user/project/repository/push_rules.md +++ b/doc/user/project/repository/push_rules.md @@ -232,7 +232,7 @@ These examples use regex (regular expressions) string boundary characters to mat the beginning of a string (`^`), and its end (`$`). They also include instances where either the directory path or the filename can include `.` or `/`. Both of these special regex characters must be escaped with a backslash `\\` if you want -to use them as normal characters in a match condition. +to use them as standard characters in a match condition. - **Prevent pushing `.exe` files to any location in the repository** - This regex matches any filename that contains `.exe` at the end: diff --git a/vendor/gems/ipynbdiff/.gitignore b/gems/ipynbdiff/.gitignore index 4f284c35a42..fae8d1c772f 100644 --- a/vendor/gems/ipynbdiff/.gitignore +++ b/gems/ipynbdiff/.gitignore @@ -1,2 +1,3 @@ *.gem +coverage .bundle diff --git a/gems/ipynbdiff/.gitlab-ci.yml b/gems/ipynbdiff/.gitlab-ci.yml new file mode 100644 index 00000000000..de5c989a4a4 --- /dev/null +++ b/gems/ipynbdiff/.gitlab-ci.yml @@ -0,0 +1,4 @@ +include: + - local: gems/gem.gitlab-ci.yml + inputs: + gem_name: "ipynbdiff" diff --git a/gems/ipynbdiff/.rubocop.yml b/gems/ipynbdiff/.rubocop.yml new file mode 100644 index 00000000000..f3a696778a4 --- /dev/null +++ b/gems/ipynbdiff/.rubocop.yml @@ -0,0 +1,11 @@ +inherit_from: + - ../config/rubocop.yml + +CodeReuse/ActiveRecord: + Enabled: false + +Naming/FileName: + Exclude: + - spec/**/*.rb + - lib/gitlab/rspec.rb + - lib/gitlab/rspec/all.rb diff --git a/vendor/gems/ipynbdiff/Gemfile b/gems/ipynbdiff/Gemfile index 7f4f5e950d1..7f4f5e950d1 100644 --- a/vendor/gems/ipynbdiff/Gemfile +++ b/gems/ipynbdiff/Gemfile diff --git a/vendor/gems/ipynbdiff/Gemfile.lock b/gems/ipynbdiff/Gemfile.lock index 6a8d3750602..1c52ff8c829 100644 --- a/vendor/gems/ipynbdiff/Gemfile.lock +++ b/gems/ipynbdiff/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - ipynbdiff (0.4.7) + gitlab-ipynbdiff (0.4.7) diffy (~> 3.4) oj (~> 3.13.16) @@ -15,9 +15,10 @@ GEM coderay (1.1.3) diff-lcs (1.5.0) diffy (3.4.2) + docile (1.4.0) memory_profiler (1.0.0) method_source (1.0.0) - oj (3.13.16) + oj (3.13.23) parser (3.1.2.0) ast (~> 2.4.1) proc_to_ast (0.1.0) @@ -47,6 +48,12 @@ GEM rspec (>= 2.13, < 4) unparser rspec-support (3.11.0) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.12.3) + simplecov_json_formatter (0.1.4) unparser (0.6.5) diff-lcs (~> 1.3) parser (>= 3.1.0) @@ -57,11 +64,12 @@ PLATFORMS DEPENDENCIES benchmark-memory (~> 0.2.0) bundler (~> 2.2) - ipynbdiff! + gitlab-ipynbdiff! pry (~> 0.14) rake (~> 13.0) rspec (~> 3.10) rspec-parameterized (~> 0.5.1) + simplecov BUNDLED WITH 2.3.16 diff --git a/vendor/gems/ipynbdiff/LICENSE b/gems/ipynbdiff/LICENSE index e6de2f90864..e6de2f90864 100644 --- a/vendor/gems/ipynbdiff/LICENSE +++ b/gems/ipynbdiff/LICENSE diff --git a/vendor/gems/ipynbdiff/README.md b/gems/ipynbdiff/README.md index f046f678a4d..f046f678a4d 100644 --- a/vendor/gems/ipynbdiff/README.md +++ b/gems/ipynbdiff/README.md diff --git a/vendor/gems/ipynbdiff/ipynbdiff.gemspec b/gems/ipynbdiff/ipynbdiff.gemspec index 014005029ef..1ec68557a05 100644 --- a/vendor/gems/ipynbdiff/ipynbdiff.gemspec +++ b/gems/ipynbdiff/ipynbdiff.gemspec @@ -1,34 +1,32 @@ # frozen_string_literal: true -lib = File.expand_path('lib/..', __dir__) -$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib) +$LOAD_PATH.push File.expand_path('lib', __dir__ || '') -require 'lib/version' +require_relative 'lib/ipynb_diff/version' Gem::Specification.new do |s| s.name = 'ipynbdiff' - s.version = IpynbDiff::VERSION + s.version = IpynbDiff::Version::VERSION s.summary = 'Human Readable diffs for Jupyter Notebooks' s.description = 'Better diff for Jupyter Notebooks by first preprocessing them and removing clutter' s.authors = ['Eduardo Bonet'] s.email = 'ebonet@gitlab.com' # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - s.files = Dir.glob("lib/**/*.*") - s.test_files = Dir.glob("spec/**/*.*") - s.homepage = - 'https://gitlab.com/gitlab-org/incubation-engineering/mlops/rb-ipynbdiff' - s.license = 'MIT' - - s.require_paths = ['lib'] + s.files = Dir['lib/**/*.rb'] + s.require_paths = ["lib"] + s.required_ruby_version = ">= 3.0" + s.homepage = 'https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/ipynbdiff' + s.license = 'MIT' s.add_runtime_dependency 'diffy', '~> 3.4' s.add_runtime_dependency 'oj', '~> 3.13.16' + s.add_development_dependency 'benchmark-memory', '~>0.2.0' s.add_development_dependency 'bundler', '~> 2.2' s.add_development_dependency 'pry', '~> 0.14' s.add_development_dependency 'rake', '~> 13.0' s.add_development_dependency 'rspec', '~> 3.10' s.add_development_dependency 'rspec-parameterized', '~> 0.5.1' - s.add_development_dependency 'benchmark-memory', '~>0.2.0' + s.add_development_dependency 'simplecov', '~> 0.12.0' end diff --git a/vendor/gems/ipynbdiff/lib/ipynbdiff.rb b/gems/ipynbdiff/lib/ipynb_diff.rb index 1765e434bf9..605ff6e4a75 100644 --- a/vendor/gems/ipynbdiff/lib/ipynbdiff.rb +++ b/gems/ipynbdiff/lib/ipynb_diff.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true +require 'ipynb_diff/transformer' +require 'ipynb_diff/diff' +require 'ipynb_diff/symbol_map' + # Human Readable Jupyter Diffs module IpynbDiff - require 'transformer' - require 'diff' - def self.diff(from, to, raise_if_invalid_nb: false, include_frontmatter: false, hide_images: false, diffy_opts: {}) transformer = Transformer.new(include_frontmatter: include_frontmatter, hide_images: hide_images) diff --git a/vendor/gems/ipynbdiff/lib/diff.rb b/gems/ipynbdiff/lib/ipynb_diff/diff.rb index 3554ac55d99..3554ac55d99 100644 --- a/vendor/gems/ipynbdiff/lib/diff.rb +++ b/gems/ipynbdiff/lib/ipynb_diff/diff.rb diff --git a/vendor/gems/ipynbdiff/lib/output_transformer.rb b/gems/ipynbdiff/lib/ipynb_diff/output_transformer.rb index 57e8a9edce3..95dbcecf95c 100644 --- a/vendor/gems/ipynbdiff/lib/output_transformer.rb +++ b/gems/ipynbdiff/lib/ipynb_diff/output_transformer.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true +require 'ipynb_diff/symbolized_markdown_helper' + module IpynbDiff # Transforms Jupyter output data into markdown class OutputTransformer - require 'symbolized_markdown_helper' include SymbolizedMarkdownHelper HIDDEN_IMAGE_OUTPUT = ' [Hidden Image Output]' @@ -32,7 +33,7 @@ module IpynbDiff def transform_error(traceback, symbol) traceback.map.with_index do |t, idx| t.split("\n").map do |l| - _(symbol / idx, l.gsub(/\[[0-9][0-9;]*m/, '').sub("\u001B", ' ').gsub(/\u001B/, '').rstrip) + ___(symbol / idx, l.gsub(/\[[0-9][0-9;]*m/, '').sub("\u001B", ' ').delete("\u001B").rstrip) end end end @@ -47,22 +48,22 @@ module IpynbDiff new_symbol = symbol_prefix / output_type case output_type when 'image/png', 'image/jpeg' - transform_image(output_type + ';base64', output_element, new_symbol) + transform_image("#{output_type};base64", output_element, new_symbol) when 'image/svg+xml' - transform_image(output_type + ';utf8', output_element, new_symbol) + transform_image("#{output_type};utf8", output_element, new_symbol) when 'text/markdown', 'text/latex', 'text/plain', 'text' transform_text(output_element, new_symbol) end end def transform_image(image_type, image_content, symbol) - return _(nil, HIDDEN_IMAGE_OUTPUT) if @hide_images + return ___(nil, HIDDEN_IMAGE_OUTPUT) if @hide_images lines = image_content.is_a?(Array) ? image_content : [image_content] single_line = lines.map(&:strip).join.gsub(/\s+/, ' ') - _(symbol, " ![](data:#{image_type},#{single_line})") + ___(symbol, " ![](data:#{image_type},#{single_line})") end def transform_text(text_content, symbol) diff --git a/vendor/gems/ipynbdiff/lib/symbol_map.rb b/gems/ipynbdiff/lib/ipynb_diff/symbol_map.rb index 89cbccbed1b..383f1de5c18 100644 --- a/vendor/gems/ipynbdiff/lib/symbol_map.rb +++ b/gems/ipynbdiff/lib/ipynb_diff/symbol_map.rb @@ -36,6 +36,7 @@ module IpynbDiff # .obj1.2.obj3.obj4 -> 9 # class SymbolMap + # rubocop:disable Lint/UnusedMethodArgument class << self def handler @handler ||= SymbolMap.new @@ -87,7 +88,7 @@ module IpynbDiff if key.nil? # value in an array if @current_path.empty? @current_path = [''] - return nil + return end symbol = @current_array_index.last @@ -103,5 +104,6 @@ module IpynbDiff @symbols = {} @current_array_index = [] end + # rubocop:enable Lint/UnusedMethodArgument end end diff --git a/vendor/gems/ipynbdiff/lib/symbolized_markdown_helper.rb b/gems/ipynbdiff/lib/ipynb_diff/symbolized_markdown_helper.rb index c075aa0d878..991c9e493bc 100644 --- a/vendor/gems/ipynbdiff/lib/symbolized_markdown_helper.rb +++ b/gems/ipynbdiff/lib/ipynb_diff/symbolized_markdown_helper.rb @@ -3,16 +3,15 @@ module IpynbDiff # Helper functions module SymbolizedMarkdownHelper - - def _(symbol = nil, content = '') + def ___(symbol = nil, content = '') { symbol: symbol, content: content } end - def symbolize_array(symbol, content, &block) + def symbolize_array(symbol, content) if content.is_a?(Array) - content.map.with_index { |l, idx| _(symbol / idx, block.call(l)) } + content.map.with_index { |l, idx| ___(symbol / idx, yield(l)) } else - content.split("\n").map { |c| _(symbol, c) } + content.split("\n").map { |c| ___(symbol, c) } end end end diff --git a/vendor/gems/ipynbdiff/lib/transformed_notebook.rb b/gems/ipynbdiff/lib/ipynb_diff/transformed_notebook.rb index f98e5f68086..f98e5f68086 100644 --- a/vendor/gems/ipynbdiff/lib/transformed_notebook.rb +++ b/gems/ipynbdiff/lib/ipynb_diff/transformed_notebook.rb diff --git a/vendor/gems/ipynbdiff/lib/transformer.rb b/gems/ipynbdiff/lib/ipynb_diff/transformer.rb index 9e666a20aa5..2b386168b5d 100644 --- a/vendor/gems/ipynbdiff/lib/transformer.rb +++ b/gems/ipynbdiff/lib/ipynb_diff/transformer.rb @@ -1,19 +1,18 @@ # frozen_string_literal: true -module IpynbDiff - require 'oj' +require 'json' +require 'yaml' +require 'ipynb_diff/output_transformer' +require 'ipynb_diff/symbolized_markdown_helper' +require 'ipynb_diff/symbol_map' +require 'ipynb_diff/transformed_notebook' +require 'oj' - class InvalidNotebookError < StandardError - end +module IpynbDiff + InvalidNotebookError = Class.new(StandardError) # Returns a markdown version of the Jupyter Notebook class Transformer - require 'json' - require 'yaml' - require 'output_transformer' - require 'symbolized_markdown_helper' - require 'symbol_map' - require 'transformed_notebook' include SymbolizedMarkdownHelper @include_frontmatter = true @@ -60,10 +59,10 @@ module IpynbDiff type = cell['cell_type'] || 'raw' [ - _(symbol, %(%% Cell type:#{type} id:#{cell['id']} tags:#{tags&.join(',')})), - _, + ___(symbol, %(%% Cell type:#{type} id:#{cell['id']} tags:#{tags&.join(',')})), + ___, rows, - _ + ___ ] end @@ -73,9 +72,9 @@ module IpynbDiff def transform_code_cell(cell, notebook, symbol) [ - _(symbol / 'source', %(``` #{notebook.dig('metadata', 'kernelspec', 'language') || ''})), + ___(symbol / 'source', %(``` #{notebook.dig('metadata', 'kernelspec', 'language') || ''})), symbolize_array(symbol / 'source', cell['source'], &:rstrip), - _(nil, '```'), + ___(nil, '```'), transform_outputs(cell['outputs'], symbol) ] end @@ -84,10 +83,10 @@ module IpynbDiff transformed = outputs.map .with_index { |output, i| @out_transformer.transform(output, symbol / ['outputs', i]) } .compact - .map { |el| [_, el] } + .map { |el| [___, el] } [ - transformed.empty? ? [] : [_, _(symbol / 'outputs', '%% Output')], + transformed.empty? ? [] : [___, ___(symbol / 'outputs', '%% Output')], transformed ] end @@ -106,7 +105,7 @@ module IpynbDiff } }.to_yaml - as_yaml.split("\n").map { |l| _(nil, l) }.append(_(nil, '---'), _) + as_yaml.split("\n").map { |l| ___(nil, l) }.append(___(nil, '---'), ___) end end end diff --git a/vendor/gems/ipynbdiff/lib/version.rb b/gems/ipynbdiff/lib/ipynb_diff/version.rb index 1451bb4ef32..1a407f9c0fa 100644 --- a/vendor/gems/ipynbdiff/lib/version.rb +++ b/gems/ipynbdiff/lib/ipynb_diff/version.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true module IpynbDiff - VERSION = '0.4.7' + module Version + VERSION = '0.4.7' + end end diff --git a/vendor/gems/ipynbdiff/spec/benchmark.rb b/gems/ipynbdiff/spec/benchmark.rb index 99f088f2056..514c8700183 100644 --- a/vendor/gems/ipynbdiff/spec/benchmark.rb +++ b/gems/ipynbdiff/spec/benchmark.rb @@ -1,8 +1,11 @@ +# frozen_string_literal: true + require 'ipynbdiff' require 'benchmark' require 'benchmark/memory' require_relative 'test_helper' +# rubocop:disable Layout/LineLength large_cell = '{ "cell_type": "code", "execution_count": 9, @@ -26,6 +29,7 @@ large_cell = '{ "do_plot(is_sin = False)" ] },' +# rubocop:enable Layout/LineLength base = '{ "cells": [ diff --git a/gems/ipynbdiff/spec/ipynb_diff/symbol_map_spec.rb b/gems/ipynbdiff/spec/ipynb_diff/symbol_map_spec.rb new file mode 100644 index 00000000000..66fb7af66af --- /dev/null +++ b/gems/ipynbdiff/spec/ipynb_diff/symbol_map_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require_relative '../test_helper' + +describe IpynbDiff::SymbolMap do + def res(*cases) + cases&.to_h || [] + end + + describe '.parse' do + subject { described_class.parse(JSON.pretty_generate(source)) } + + context 'when object has blank key' do + let(:source) { { "": { "": 5 } } } + + it { is_expected.to match_array(res([".", 2], ["..", 3])) } + end + + context 'when object is empty' do + let(:source) { {} } + + it { is_expected.to be_empty } + end + + context 'when object is empty array' do + let(:source) { [] } + + it { is_expected.to be_empty } + end + + context 'when object has inner object and number' do + let(:source) { { obj1: { obj2: 1 } } } + + it { is_expected.to match_array(res(['.obj1', 2], ['.obj1.obj2', 3])) } + end + + context 'when object has inner object and number, string and array with object' do + let(:source) { { obj1: { obj2: [123, 2, true], obj3: "hel\nlo", obj4: true, obj5: 123, obj6: 'a' } } } + + it do + is_expected.to match_array( + res(['.obj1', 2], + ['.obj1.obj2', 3], + ['.obj1.obj2.0', 4], + ['.obj1.obj2.1', 5], + ['.obj1.obj2.2', 6], + ['.obj1.obj3', 8], + ['.obj1.obj4', 9], + ['.obj1.obj5', 10], + ['.obj1.obj6', 11]) + ) + end + end + end +end diff --git a/gems/ipynbdiff/spec/ipynb_diff/transformer_spec.rb b/gems/ipynbdiff/spec/ipynb_diff/transformer_spec.rb new file mode 100644 index 00000000000..214a8192542 --- /dev/null +++ b/gems/ipynbdiff/spec/ipynb_diff/transformer_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require_relative '../test_helper' + +describe IpynbDiff::Transformer do + describe '.transform' do + using RSpec::Parameterized::TableSyntax + + let!(:default_config) { { include_frontmatter: false, hide_images: false } } + + let(:test_case) { read_test_case(test_case_name) } + let(:notebook) { test_case[:input] || FROM_IPYNB } + let(:config) { {} } + + subject { described_class.new(**default_config.merge(config)).transform(notebook) } + + where(:ctx, :test_case_name, :config) do + 'renders metadata' | 'no_cells' | { include_frontmatter: true } + 'is empty for no cells, but metadata is false' | 'no_cells_no_metadata' | {} + 'adds markdown cell' | 'only_md' | {} + 'adds block with only one line of markdown' | 'single_line_md' | {} + 'adds raw block' | 'only_raw' | {} + 'code cell, but no output' | 'only_code' | {} + 'code cell, but no language' | 'only_code_no_language' | {} + 'code cell, but no kernelspec' | 'only_code_no_kernelspec' | {} + 'code cell, but no nb metadata' | 'only_code_no_metadata' | {} + 'text output' | 'text_output' | {} + 'ignores html output' | 'ignore_html_output' | {} + 'extracts png output along with text' | 'text_png_output' | {} + 'embeds svg as image' | 'svg' | {} + 'extracts latex output' | 'latex_output' | {} + 'extracts error output' | 'error_output' | {} + 'does not fetch tags if there is no cell metadata' | 'no_metadata_on_cell' | {} + 'generates :percent decorator' | 'percent_decorator' | {} + 'parses stream output' | 'stream_text' | {} + 'ignores unknown output type' | 'unknown_output_type' | {} + 'handles backslash correctly' | 'backslash_as_last_char' | {} + 'multiline png output' | 'multiline_png_output' | {} + 'hides images when option passed' | 'hide_images' | { hide_images: true } + '\n within source lines' | 'source_with_linebreak' | { hide_images: true } + end + + with_them do + it 'generates the expected markdown' do + expect(subject.as_text).to eq test_case[:expected_markdown] + end + + it 'marks the lines correctly' do + blocks = subject.blocks.map { |b| b[:source_symbol] }.join("\n") + + expect(blocks).to eq test_case[:expected_symbols] + end + end + + describe 'Source line map' do + let(:config) { { include_frontmatter: false } } + let(:test_case_name) { 'text_png_output' } + + it 'generates the correct transformed to source line map' do + line_numbers = subject.blocks.map { |b| b[:source_line] }.join("\n") + + expect(line_numbers).to eq test_case[:expected_line_numbers] + end + end + + context 'when json is invalid' do + let(:notebook) { 'a' } + + it 'raises error' do + expect { subject }.to raise_error(IpynbDiff::InvalidNotebookError) + end + end + + context 'when it does not have the cell tag' do + let(:notebook) { '{"metadata":[]}' } + + it 'raises error' do + expect { subject }.to raise_error(IpynbDiff::InvalidNotebookError) + end + end + + context 'when notebook can not be parsed' do + let(:notebook) { '{"cells":[]}' } + + before do + allow(Oj::Parser.usual).to receive(:parse).and_return(nil) + end + + it 'raises error' do + expect { subject }.to raise_error(IpynbDiff::InvalidNotebookError) + end + end + end +end diff --git a/vendor/gems/ipynbdiff/spec/ipynbdiff_spec.rb b/gems/ipynbdiff/spec/ipynb_diff_spec.rb index 1c2a2188edf..44fcd99f131 100644 --- a/vendor/gems/ipynbdiff/spec/ipynbdiff_spec.rb +++ b/gems/ipynbdiff/spec/ipynb_diff_spec.rb @@ -1,59 +1,55 @@ # frozen_string_literal: true -require 'ipynbdiff' -require 'rspec' -require 'rspec-parameterized' - -BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'testdata') +require_relative 'test_helper' describe IpynbDiff do def diff_signs(diff) - diff.to_s(:text).scan(/.*\n/).map { |l| l[0] }.join('') + diff.to_s(:text).scan(/.*\n/).map { |l| l[0] }.join('') # rubocop:disable Rails/Pluck end - describe 'diff' do - let(:from_path) { File.join(BASE_PATH, 'from.ipynb') } - let(:to_path) { File.join(BASE_PATH,'to.ipynb') } + describe '.diff' do + let(:from_path) { FROM_PATH } + let(:to_path) { TO_PATH } let(:from) { File.read(from_path) } let(:to) { File.read(to_path) } let(:include_frontmatter) { false } let(:hide_images) { false } - subject { IpynbDiff.diff(from, to, include_frontmatter: include_frontmatter, hide_images: hide_images) } + subject { described_class.diff(from, to, include_frontmatter: include_frontmatter, hide_images: hide_images) } context 'if preprocessing is active' do it 'html tables are stripped' do - is_expected.to_not include('<td>') + is_expected.not_to include('<td>') end end context 'when to is nil' do let(:to) { nil } - let(:from_path) { File.join(BASE_PATH, 'only_md', 'input.ipynb') } + let(:from_path) { test_case_input_path('only_md') } it 'all lines are removals' do expect(diff_signs(subject)).to eq('-----') end end - context 'when to is nil' do + context 'when from is nil' do let(:from) { nil } - let(:to_path) { File.join(BASE_PATH, 'only_md', 'input.ipynb') } + let(:to_path) { test_case_input_path('only_md') } it 'all lines are additions' do expect(diff_signs(subject)).to eq('+++++') end end - context 'When include_frontmatter is true' do + context 'when include_frontmatter is true' do let(:include_frontmatter) { true } - it 'should show changes metadata in the metadata' do + it 'shows changes metadata in the metadata' do expect(subject.to_s(:text)).to include('+ display_name: New Python 3 (ipykernel)') end end - context 'When hide_images is true' do + context 'when hide_images is true' do let(:hide_images) { true } it 'hides images' do @@ -61,9 +57,9 @@ describe IpynbDiff do end end - context 'When include_frontmatter is false' do - it 'should drop metadata from the diff' do - expect(subject.to_s(:text)).to_not include('+ display_name: New Python 3 (ipykernel)') + context 'when include_frontmatter is false' do + it 'drops metadata from the diff' do + expect(subject.to_s(:text)).not_to include('+ display_name: New Python 3 (ipykernel)') end end @@ -83,40 +79,47 @@ describe IpynbDiff do end end - describe 'transform' do - [nil, 'a', '{"metadata":[]}'].each do |invalid_nb| - context "when json is invalid (#{invalid_nb || 'nil'})" do - it 'is nil' do - expect(IpynbDiff.transform(invalid_nb)).to be_nil - end - end + describe '.transform' do + let(:notebook) { FROM_IPYNB } + let(:include_frontmatter) { false } + let(:hide_images) { false } + + subject do + described_class.transform(notebook, + include_frontmatter: include_frontmatter, + hide_images: hide_images) end - context 'options' do - let(:include_frontmatter) { false } - let(:hide_images) { false } + describe 'error cases' do + using RSpec::Parameterized::TableSyntax + + where(:ctx, :notebook) do + 'notebook is nil' | nil + 'notebook is invalid' | 'a' + 'notebook does not have cell' | '{"metadata":[]}' + end - subject do - IpynbDiff.transform(File.read(File.join(BASE_PATH, 'from.ipynb')), - include_frontmatter: include_frontmatter, - hide_images: hide_images) + with_them do + it { is_expected.to be_nil } end + end - context 'include_frontmatter is false' do - it { is_expected.to_not include('display_name: Python 3 (ipykernel)') } + describe 'options' do + context 'when include_frontmatter is false' do + it { is_expected.not_to include('display_name: Python 3 (ipykernel)') } end - context 'include_frontmatter is true' do + context 'when include_frontmatter is true' do let(:include_frontmatter) { true } it { is_expected.to include('display_name: Python 3 (ipykernel)') } end - context 'hide_images is false' do + context 'when hide_images is false' do it { is_expected.not_to include('[Hidden Image Output]') } end - context 'hide_images is true' do + context 'when hide_images is true' do let(:hide_images) { true } it { is_expected.to include(' [Hidden Image Output]') } diff --git a/gems/ipynbdiff/spec/test_helper.rb b/gems/ipynbdiff/spec/test_helper.rb new file mode 100644 index 00000000000..626b72b99f0 --- /dev/null +++ b/gems/ipynbdiff/spec/test_helper.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'simplecov' +SimpleCov.start + +require 'ipynb_diff' +require 'rspec' +require 'rspec-parameterized' +require 'json' + +BASE_PATH = File.join(__dir__ || '', 'testdata') + +FROM_PATH = File.join(BASE_PATH, 'from.ipynb') +TO_PATH = File.join(BASE_PATH, 'to.ipynb') + +FROM_IPYNB = File.read(FROM_PATH) +TO_IPYNB = File.read(TO_PATH) + +def test_case_input_path(test_case) + File.join(BASE_PATH, test_case, 'input.ipynb') +end + +def test_case_symbols_path(test_case) + File.join(BASE_PATH, test_case, 'expected_symbols.txt') +end + +def test_case_md_path(test_case) + File.join(BASE_PATH, test_case, 'expected.md') +end + +def test_case_line_numbers_path(test_case) + File.join(BASE_PATH, test_case, 'expected_line_numbers.txt') +end + +def read_file_if_exists(path) + File.read(path) if File.file?(path) +end + +def read_test_case(test_case_name) + { + input: read_file_if_exists(test_case_input_path(test_case_name)), + expected_markdown: read_file_if_exists(test_case_md_path(test_case_name)), + expected_symbols: read_file_if_exists(test_case_symbols_path(test_case_name)), + expected_line_numbers: read_file_if_exists(test_case_line_numbers_path(test_case_name)) + } +end diff --git a/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected.md b/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected.md index 299e286c679..299e286c679 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected.md +++ b/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected_symbols.txt index 6fa29ae28de..6fa29ae28de 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/input.ipynb b/gems/ipynbdiff/spec/testdata/backslash_as_last_char/input.ipynb index 0714044e3ae..0714044e3ae 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/backslash_as_last_char/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md b/gems/ipynbdiff/spec/testdata/error_output/expected.md index e6e8a075598..e6e8a075598 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md +++ b/gems/ipynbdiff/spec/testdata/error_output/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt index 5d2f248135d..5d2f248135d 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/error_output/input.ipynb b/gems/ipynbdiff/spec/testdata/error_output/input.ipynb index 45ee81a0e2d..45ee81a0e2d 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/error_output/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/error_output/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/from.ipynb b/gems/ipynbdiff/spec/testdata/from.ipynb index 68a4b11cbbc..68a4b11cbbc 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/from.ipynb +++ b/gems/ipynbdiff/spec/testdata/from.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md b/gems/ipynbdiff/spec/testdata/hide_images/expected.md index ff63d351a3b..ff63d351a3b 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md +++ b/gems/ipynbdiff/spec/testdata/hide_images/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt index b8f24f9fba5..b8f24f9fba5 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/hide_images/input.ipynb b/gems/ipynbdiff/spec/testdata/hide_images/input.ipynb index dab0e5bb9cf..dab0e5bb9cf 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/hide_images/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/hide_images/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md b/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md index 3085da739ed..3085da739ed 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md +++ b/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt index 3bf319d1fa6..3bf319d1fa6 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/input.ipynb b/gems/ipynbdiff/spec/testdata/ignore_html_output/input.ipynb index 26117a78934..26117a78934 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/ignore_html_output/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md b/gems/ipynbdiff/spec/testdata/latex_output/expected.md index 194c1f43c42..194c1f43c42 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md +++ b/gems/ipynbdiff/spec/testdata/latex_output/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt index 868adca2712..868adca2712 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/latex_output/input.ipynb b/gems/ipynbdiff/spec/testdata/latex_output/input.ipynb index f8ff3e72beb..f8ff3e72beb 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/latex_output/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/latex_output/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md b/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md index 0a69c8370e7..0a69c8370e7 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md +++ b/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt index 1b66012ef20..1b66012ef20 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/input.ipynb b/gems/ipynbdiff/spec/testdata/multiline_png_output/input.ipynb index 4d19a504553..4d19a504553 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/multiline_png_output/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected.md b/gems/ipynbdiff/spec/testdata/no_cells/expected.md index b7c09c51fb8..b7c09c51fb8 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected.md +++ b/gems/ipynbdiff/spec/testdata/no_cells/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/no_cells/expected_symbols.txt index a60f3032882..a60f3032882 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/no_cells/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells/input.ipynb b/gems/ipynbdiff/spec/testdata/no_cells/input.ipynb index c2ba0ebf50a..c2ba0ebf50a 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/no_cells/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/no_cells/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected.md b/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected.md index e69de29bb2d..e69de29bb2d 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected.md +++ b/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected_symbols.txt index e69de29bb2d..e69de29bb2d 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/input.ipynb b/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/input.ipynb index c2ba0ebf50a..c2ba0ebf50a 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected.md b/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected.md index d9d72bf8f76..d9d72bf8f76 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected.md +++ b/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected_symbols.txt index a7000494a1b..a7000494a1b 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/input.ipynb b/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/input.ipynb index 62060124a2a..62060124a2a 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code/expected.md b/gems/ipynbdiff/spec/testdata/only_code/expected.md index 124b8217a6a..124b8217a6a 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code/expected.md +++ b/gems/ipynbdiff/spec/testdata/only_code/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/only_code/expected_symbols.txt index 59b11103195..59b11103195 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/only_code/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code/input.ipynb b/gems/ipynbdiff/spec/testdata/only_code/input.ipynb index a93108dccb8..a93108dccb8 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/only_code/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected.md b/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected.md index c6d8e13fc3a..c6d8e13fc3a 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected.md +++ b/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected_symbols.txt index 2e902582e14..2e902582e14 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/input.ipynb b/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/input.ipynb index c3ff71057a6..c3ff71057a6 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected.md b/gems/ipynbdiff/spec/testdata/only_code_no_language/expected.md index c6d8e13fc3a..c6d8e13fc3a 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected.md +++ b/gems/ipynbdiff/spec/testdata/only_code_no_language/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/only_code_no_language/expected_symbols.txt index 2e902582e14..2e902582e14 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/only_code_no_language/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/input.ipynb b/gems/ipynbdiff/spec/testdata/only_code_no_language/input.ipynb index fb16b106cbe..fb16b106cbe 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/only_code_no_language/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected.md b/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected.md index c6d8e13fc3a..c6d8e13fc3a 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected.md +++ b/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected_symbols.txt index 2e902582e14..2e902582e14 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/input.ipynb b/gems/ipynbdiff/spec/testdata/only_code_no_metadata/input.ipynb index 364c080168b..364c080168b 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/only_code_no_metadata/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_md/expected.md b/gems/ipynbdiff/spec/testdata/only_md/expected.md index bdf4db5aea5..bdf4db5aea5 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_md/expected.md +++ b/gems/ipynbdiff/spec/testdata/only_md/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_md/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/only_md/expected_symbols.txt index d3d6d526fc3..d3d6d526fc3 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_md/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/only_md/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_md/input.ipynb b/gems/ipynbdiff/spec/testdata/only_md/input.ipynb index 9d6b550af31..9d6b550af31 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_md/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/only_md/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected.md b/gems/ipynbdiff/spec/testdata/only_raw/expected.md index 91c476e843b..91c476e843b 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected.md +++ b/gems/ipynbdiff/spec/testdata/only_raw/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/only_raw/expected_symbols.txt index bceaf355c2f..bceaf355c2f 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/only_raw/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_raw/input.ipynb b/gems/ipynbdiff/spec/testdata/only_raw/input.ipynb index 750e1bba615..750e1bba615 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/only_raw/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/only_raw/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md b/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md index 1ece1f2fd06..1ece1f2fd06 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md +++ b/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt index c95665d1903..c95665d1903 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected.md b/gems/ipynbdiff/spec/testdata/single_line_md/expected.md index 392a5048f59..392a5048f59 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected.md +++ b/gems/ipynbdiff/spec/testdata/single_line_md/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/single_line_md/expected_symbols.txt index 86a7f6b3960..86a7f6b3960 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/single_line_md/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/single_line_md/input.ipynb b/gems/ipynbdiff/spec/testdata/single_line_md/input.ipynb index 5ebd41adbfa..5ebd41adbfa 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/single_line_md/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/single_line_md/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/source_with_linebreak/expected.md b/gems/ipynbdiff/spec/testdata/source_with_linebreak/expected.md index 180fffe24ce..180fffe24ce 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/source_with_linebreak/expected.md +++ b/gems/ipynbdiff/spec/testdata/source_with_linebreak/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/source_with_linebreak/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/source_with_linebreak/expected_symbols.txt index 1e8bdda4b9a..1e8bdda4b9a 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/source_with_linebreak/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/source_with_linebreak/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/source_with_linebreak/input.ipynb b/gems/ipynbdiff/spec/testdata/source_with_linebreak/input.ipynb index faacc703969..faacc703969 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/source_with_linebreak/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/source_with_linebreak/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md b/gems/ipynbdiff/spec/testdata/stream_text/expected.md index 0448bf21111..0448bf21111 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md +++ b/gems/ipynbdiff/spec/testdata/stream_text/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt index be4e2861ea9..be4e2861ea9 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/stream_text/input.ipynb b/gems/ipynbdiff/spec/testdata/stream_text/input.ipynb index 14601fe35e5..14601fe35e5 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/stream_text/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/stream_text/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/svg/expected.md b/gems/ipynbdiff/spec/testdata/svg/expected.md index a5a167d31c5..a5a167d31c5 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/svg/expected.md +++ b/gems/ipynbdiff/spec/testdata/svg/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt index 861198a8c92..861198a8c92 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/svg/input.ipynb b/gems/ipynbdiff/spec/testdata/svg/input.ipynb index a02d01f7bf2..a02d01f7bf2 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/svg/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/svg/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md b/gems/ipynbdiff/spec/testdata/text_output/expected.md index 1b6c086ecd5..1b6c086ecd5 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md +++ b/gems/ipynbdiff/spec/testdata/text_output/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt index a004d852ba4..a004d852ba4 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_output/input.ipynb b/gems/ipynbdiff/spec/testdata/text_output/input.ipynb index b1b387bb99d..b1b387bb99d 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_output/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/text_output/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md b/gems/ipynbdiff/spec/testdata/text_png_output/expected.md index c77f109378c..c77f109378c 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md +++ b/gems/ipynbdiff/spec/testdata/text_png_output/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_line_numbers.txt b/gems/ipynbdiff/spec/testdata/text_png_output/expected_line_numbers.txt index 62e35deb96d..62e35deb96d 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_line_numbers.txt +++ b/gems/ipynbdiff/spec/testdata/text_png_output/expected_line_numbers.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt index 49f2d7149d8..49f2d7149d8 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/input.ipynb b/gems/ipynbdiff/spec/testdata/text_png_output/input.ipynb index 3728b129d26..3728b129d26 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/text_png_output/input.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/to.ipynb b/gems/ipynbdiff/spec/testdata/to.ipynb index 99b51f3b857..99b51f3b857 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/to.ipynb +++ b/gems/ipynbdiff/spec/testdata/to.ipynb diff --git a/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected.md b/gems/ipynbdiff/spec/testdata/unknown_output_type/expected.md index af34d6eb8c3..af34d6eb8c3 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected.md +++ b/gems/ipynbdiff/spec/testdata/unknown_output_type/expected.md diff --git a/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected_symbols.txt b/gems/ipynbdiff/spec/testdata/unknown_output_type/expected_symbols.txt index cb35f88c897..cb35f88c897 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected_symbols.txt +++ b/gems/ipynbdiff/spec/testdata/unknown_output_type/expected_symbols.txt diff --git a/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/input.ipynb b/gems/ipynbdiff/spec/testdata/unknown_output_type/input.ipynb index 42f4b39b365..42f4b39b365 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/input.ipynb +++ b/gems/ipynbdiff/spec/testdata/unknown_output_type/input.ipynb diff --git a/lib/gitlab/application_context.rb b/lib/gitlab/application_context.rb index 0ea52b7b7c8..67fc2ae2fcc 100644 --- a/lib/gitlab/application_context.rb +++ b/lib/gitlab/application_context.rb @@ -47,11 +47,16 @@ module Gitlab Attribute.new(:root_caller_id, String), Attribute.new(:merge_action_status, String) ].freeze + private_constant :APPLICATION_ATTRIBUTES def self.known_keys KNOWN_KEYS end + def self.application_attributes + APPLICATION_ATTRIBUTES + end + def self.with_context(args, &block) application_context = new(**args) application_context.use(&block) @@ -79,12 +84,13 @@ module Gitlab end def initialize(**args) - unknown_attributes = args.keys - APPLICATION_ATTRIBUTES.map(&:name) + unknown_attributes = args.keys - self.class.application_attributes.map(&:name) raise ArgumentError, "#{unknown_attributes} are not known keys" if unknown_attributes.any? @set_values = args.keys assign_attributes(args) + set_attr_readers end # rubocop: disable Metrics/CyclomaticComplexity @@ -122,12 +128,14 @@ module Gitlab attr_reader :set_values - APPLICATION_ATTRIBUTES.each do |attr| - lazy_attr_reader attr.name, type: attr.type + def set_attr_readers + self.class.application_attributes.each do |attr| + self.class.lazy_attr_reader attr.name, type: attr.type + end end def assign_hash_if_value(hash, attribute_name) - unless KNOWN_KEYS.include?(attribute_name) + unless self.class.known_keys.include?(attribute_name) raise ArgumentError, "unknown attribute `#{attribute_name}`" end @@ -137,7 +145,7 @@ module Gitlab end def assign_attributes(values) - values.slice(*APPLICATION_ATTRIBUTES.map(&:name)).each do |name, value| + values.slice(*self.class.application_attributes.map(&:name)).each do |name, value| instance_variable_set("@#{name}", value) end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9b572bcf0f9..98e4547c947 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4463,9 +4463,6 @@ msgstr "" msgid "AlertSettings|Prometheus" msgstr "" -msgid "AlertSettings|Prometheus API base URL" -msgstr "" - msgid "AlertSettings|Reset Key" msgstr "" @@ -4502,9 +4499,6 @@ msgstr "" msgid "AlertSettings|To create a custom mapping, enter an example payload from your monitoring tool, in JSON format. Select the \"Parse payload fields\" button to continue." msgstr "" -msgid "AlertSettings|URL cannot be blank and must start with http: or https:." -msgstr "" - msgid "AlertSettings|Use the URL and authorization key below to configure how Prometheus sends alerts to GitLab. Review the %{linkStart}GitLab documentation%{linkEnd} to learn how to configure your endpoint." msgstr "" @@ -15217,6 +15211,9 @@ msgstr "" msgid "Dependency list" msgstr "" +msgid "DependencyProxy|%{docLinkStart}See the documentation%{docLinkEnd} for other ways to store Docker images in Dependency Proxy cache." +msgstr "" + msgid "DependencyProxy|All items in the cache are scheduled for removal." msgstr "" @@ -15235,6 +15232,9 @@ msgstr "" msgid "DependencyProxy|Contains %{count} blobs of images (%{size})" msgstr "" +msgid "DependencyProxy|Copy example" +msgstr "" + msgid "DependencyProxy|Copy image path" msgstr "" @@ -15259,6 +15259,9 @@ msgstr "" msgid "DependencyProxy|Image list" msgstr "" +msgid "DependencyProxy|Pull image by digest example" +msgstr "" + msgid "DependencyProxy|Scheduled for deletion" msgstr "" @@ -15268,6 +15271,9 @@ msgstr "" msgid "DependencyProxy|To see the image prefix and what is in the cache, visit the %{linkStart}Dependency Proxy%{linkEnd}" msgstr "" +msgid "DependencyProxy|To store docker images in Dependency Proxy cache, pull an image by tag in your %{codeStart}.gitlab-ci.yml%{codeEnd} file. In this example, the image is %{codeStart}alpine:latest%{codeEnd}" +msgstr "" + msgid "DependencyProxy|When enabled, images older than 90 days will be removed from the cache." msgstr "" @@ -40936,7 +40942,7 @@ msgstr "" msgid "SecurityApprovals|Learn more about Coverage-Check" msgstr "" -msgid "SecurityApprovals|Requires approval for decreases in test coverage. %{linkStart}Learn more.%{linkEnd}" +msgid "SecurityApprovals|Requires approval for decreases in test coverage. %{linkStart}Learn more%{linkEnd}." msgstr "" msgid "SecurityConfiguration|%{featureName} merge request creation mutation failed" @@ -42351,6 +42357,12 @@ msgstr "" msgid "ServicePing|Service ping is off" msgstr "" +msgid "ServicePing|This can be changed at any time in %{link_start}your settings%{link_end}." +msgstr "" + +msgid "ServicePing|To help improve GitLab, we would like to periodically %{link_start}collect usage information%{link_end}." +msgstr "" + msgid "ServicePing|To view instance-level analytics, ask an admin to turn on %{docLinkStart}service ping%{docLinkEnd}." msgstr "" @@ -47759,9 +47771,6 @@ msgstr "" msgid "To go to GitLab Pages, on the left sidebar, select %{pages_link}." msgstr "" -msgid "To help improve GitLab, we would like to periodically %{docs_link}. This can be changed at any time in %{settings_link}." -msgstr "" - msgid "To import an SVN repository, check out %{svn_link}." msgstr "" @@ -48485,12 +48494,6 @@ msgstr "" msgid "URL" msgstr "" -msgid "URL cannot be blank" -msgstr "" - -msgid "URL is invalid" -msgstr "" - msgid "URL is required" msgstr "" @@ -54005,9 +54008,6 @@ msgid_plural "closed issues" msgstr[0] "" msgstr[1] "" -msgid "collect usage information" -msgstr "" - msgid "comment" msgstr "" @@ -55575,9 +55575,6 @@ msgstr "" msgid "your group (%{group_name})" msgstr "" -msgid "your settings" -msgstr "" - msgid "yyyy-mm-dd" msgstr "" diff --git a/qa/qa/flow/alert_settings.rb b/qa/qa/flow/alert_settings.rb index f5ee4f94065..7bda1585b39 100644 --- a/qa/qa/flow/alert_settings.rb +++ b/qa/qa/flow/alert_settings.rb @@ -27,7 +27,6 @@ module QA alert.add_new_integration alert.select_prometheus alert.activate_integration - alert.fill_in_prometheus_url alert.save_and_create_alert end end diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 6aadf7e1fd0..92b4fd39759 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -523,6 +523,12 @@ module QA "Could not find the expected element as #{element_when_flag_enabled} or #{element_when_flag_disabled}." \ "The relevant feature flag is #{feature_flag}" end + + def wait_for_gitlab_to_respond + wait_until(sleep_interval: 5, message: '502 - GitLab is taking too much time to respond') do + Capybara.page.has_no_text?('GitLab is taking too much time to respond') + end + end end end end diff --git a/qa/qa/page/project/settings/alerts.rb b/qa/qa/page/project/settings/alerts.rb index 3ff4ef20bde..2646e271bd2 100644 --- a/qa/qa/page/project/settings/alerts.rb +++ b/qa/qa/page/project/settings/alerts.rb @@ -25,7 +25,6 @@ module QA element :save_and_create_alert_button element :test_payload_field element :send_test_alert_button - element :prometheus_url_field end def go_to_alert_settings @@ -73,19 +72,12 @@ module QA def select_prometheus click_element(:integration_type_dropdown) find("option[value='PROMETHEUS']").click - - # Click outside of the list to close it - click_element(:prometheus_url_field) end def enter_integration_name(name) fill_element(:integration_name_field, name) end - def fill_in_prometheus_url(url = Runtime::Scenario.gitlab_address) - fill_element(:prometheus_url_field, url) - end - def activate_integration within_element(:active_toggle_container) do find('.gl-toggle').click diff --git a/qa/qa/support/wait_for_requests.rb b/qa/qa/support/wait_for_requests.rb index f7983521613..2856602629a 100644 --- a/qa/qa/support/wait_for_requests.rb +++ b/qa/qa/support/wait_for_requests.rb @@ -39,12 +39,6 @@ module QA Capybara.page.has_no_css?('.gl-spinner', wait: wait) end end - - def wait_for_gitlab_to_respond - wait_until(sleep_interval: 5, message: '502 - GitLab is taking too much time to respond') do - Capybara.page.has_no_text?('GitLab is taking too much time to respond') - end - end end end end diff --git a/rubocop/cop/active_record_association_reload.rb b/rubocop/cop/active_record_association_reload.rb index 9a1e9674904..758d5de348c 100644 --- a/rubocop/cop/active_record_association_reload.rb +++ b/rubocop/cop/active_record_association_reload.rb @@ -2,7 +2,7 @@ module RuboCop module Cop - # Cop that blacklists the use of `reload`. + # Cop that denylists the use of `reload`. class ActiveRecordAssociationReload < RuboCop::Cop::Base MSG = 'Use reset instead of reload. ' \ 'For more details check the https://gitlab.com/gitlab-org/gitlab-foss/issues/60218.' diff --git a/rubocop/cop/avoid_becomes.rb b/rubocop/cop/avoid_becomes.rb index 20df394c32c..9ef623e6360 100644 --- a/rubocop/cop/avoid_becomes.rb +++ b/rubocop/cop/avoid_becomes.rb @@ -2,7 +2,7 @@ module RuboCop module Cop - # Cop that blacklists the use of ".becomes(SomeConstant)". + # Cop that denylists the use of ".becomes(SomeConstant)". # # The use of becomes() will result in a new object being created, throwing # away any eager loaded assocations. This in turn can cause N+1 query diff --git a/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb b/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb index ea7cdd5bf9d..78e405cf0cc 100644 --- a/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb +++ b/rubocop/cop/avoid_keyword_arguments_in_sidekiq_workers.rb @@ -2,7 +2,7 @@ module RuboCop module Cop - # Cop that blacklists keyword arguments usage in Sidekiq workers + # Cop that denylists keyword arguments usage in Sidekiq workers class AvoidKeywordArgumentsInSidekiqWorkers < RuboCop::Cop::Base MSG = "Do not use keyword arguments in Sidekiq workers. " \ "For details, check https://github.com/mperham/sidekiq/issues/2372" diff --git a/rubocop/cop/default_scope.rb b/rubocop/cop/default_scope.rb index 930a69be881..1656c0b1569 100644 --- a/rubocop/cop/default_scope.rb +++ b/rubocop/cop/default_scope.rb @@ -2,7 +2,7 @@ module RuboCop module Cop - # Cop that blacklists the use of `default_scope`. + # Cop that denylists the use of `default_scope`. class DefaultScope < RuboCop::Cop::Base MSG = <<~EOF Do not use `default_scope`, as it does not follow the principle of diff --git a/rubocop/cop/destroy_all.rb b/rubocop/cop/destroy_all.rb index 78e5d0f25f3..55c0bc93d43 100644 --- a/rubocop/cop/destroy_all.rb +++ b/rubocop/cop/destroy_all.rb @@ -2,7 +2,7 @@ module RuboCop module Cop - # Cop that blacklists the use of `destroy_all`. + # Cop that denylists the use of `destroy_all`. class DestroyAll < RuboCop::Cop::Base MSG = 'Use `delete_all` instead of `destroy_all`. ' \ '`destroy_all` will load the rows into memory, then execute a ' \ diff --git a/rubocop/cop/group_public_or_visible_to_user.rb b/rubocop/cop/group_public_or_visible_to_user.rb index d3aa230680b..2038bd6f4f6 100644 --- a/rubocop/cop/group_public_or_visible_to_user.rb +++ b/rubocop/cop/group_public_or_visible_to_user.rb @@ -2,7 +2,7 @@ # module RuboCop module Cop - # Cop that blacklists the usage of Group.public_or_visible_to_user + # Cop that denylists the usage of Group.public_or_visible_to_user class GroupPublicOrVisibleToUser < RuboCop::Cop::Base MSG = '`Group.public_or_visible_to_user` should be used with extreme care. ' \ 'Please ensure that you are not using it on its own and that the amount ' \ diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb index 7e3c44cc5fd..333af10d82f 100644 --- a/rubocop/cop/inject_enterprise_edition_module.rb +++ b/rubocop/cop/inject_enterprise_edition_module.rb @@ -2,7 +2,7 @@ module RuboCop module Cop - # Cop that blacklists the injecting of extension specific modules before any lines which are not already injecting another module. + # Cop that denylists the injecting of extension specific modules before any lines which are not already injecting another module. # It allows multiple module injections as long as they're all at the end. class InjectEnterpriseEditionModule < RuboCop::Cop::Base extend RuboCop::Cop::AutoCorrector @@ -80,7 +80,7 @@ module RuboCop # Automatically correcting these offenses is not always possible, as # sometimes code needs to be refactored to make this work. As such, we - # only allow developers to easily blacklist existing offenses. + # only allow developers to easily denylist existing offenses. def corrector(node) lambda do |corrector| corrector.insert_after( diff --git a/rubocop/cop/migration/add_columns_to_wide_tables.rb b/rubocop/cop/migration/add_columns_to_wide_tables.rb index 98dd605faef..ba53177b5d5 100644 --- a/rubocop/cop/migration/add_columns_to_wide_tables.rb +++ b/rubocop/cop/migration/add_columns_to_wide_tables.rb @@ -12,7 +12,7 @@ module RuboCop MSG = '`%s` is a wide table with several columns, adding more should be avoided unless absolutely necessary.' \ ' Consider storing the column in a different table or creating a new one.' - BLACKLISTED_METHODS = %i[ + DENYLISTED_METHODS = %i[ add_column add_reference add_timestamps_with_timezone @@ -33,7 +33,7 @@ module RuboCop def offense?(method_name, table_name) wide_table?(table_name) && - BLACKLISTED_METHODS.include?(method_name) + DENYLISTED_METHODS.include?(method_name) end def wide_table?(table_name) diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 660257b042a..7ba63a4464a 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -229,11 +229,11 @@ function download_chart() { else echoinfo "Downloading the GitLab chart..." true - curl --location -o gitlab.tar.bz2 "https://gitlab.com/gitlab-org/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2" + curl --location -o gitlab.tar.bz2 "${GITLAB_HELM_CHART_PROJECT_URL}/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2" tar -xjf gitlab.tar.bz2 echoinfo "Adding the gitlab repo to Helm..." - helm repo add gitlab https://charts.gitlab.io + helm repo add gitlab "${GITLAB_HELM_REPO_URL}" echoinfo "Building the gitlab chart's dependencies..." helm dependency build "gitlab-${GITLAB_HELM_CHART_REF}" @@ -261,19 +261,20 @@ function deploy() { local namespace="${CI_ENVIRONMENT_SLUG}" local release="${CI_ENVIRONMENT_SLUG}" local base_config_file_ref="${CI_DEFAULT_BRANCH}" + if [[ "$(base_config_changed)" == "true" ]]; then base_config_file_ref="${CI_COMMIT_SHA}"; fi - local base_config_file="https://gitlab.com/gitlab-org/gitlab/raw/${base_config_file_ref}/scripts/review_apps/base-config.yaml" + local base_config_file="${GITLAB_REPO_URL}/raw/${base_config_file_ref}/scripts/review_apps/base-config.yaml" echoinfo "Deploying ${release} to ${CI_ENVIRONMENT_URL} ..." true - IMAGE_REPOSITORY="registry.gitlab.com/gitlab-org/build/cng-mirror" - gitlab_toolbox_image_repository="${IMAGE_REPOSITORY}/gitlab-toolbox-ee" - gitlab_sidekiq_image_repository="${IMAGE_REPOSITORY}/gitlab-sidekiq-ee" - gitlab_webservice_image_repository="${IMAGE_REPOSITORY}/gitlab-webservice-ee" + IMAGE_REPOSITORY="${GITLAB_IMAGE_REPOSITORY}" + gitlab_toolbox_image_repository="${IMAGE_REPOSITORY}/gitlab-toolbox-${GITLAB_IMAGE_SUFFIX}" + gitlab_sidekiq_image_repository="${IMAGE_REPOSITORY}/gitlab-sidekiq-${GITLAB_IMAGE_SUFFIX}" + gitlab_webservice_image_repository="${IMAGE_REPOSITORY}/gitlab-webservice-${GITLAB_IMAGE_SUFFIX}" gitlab_gitaly_image_repository="${IMAGE_REPOSITORY}/gitaly" gitaly_image_tag=$(parse_gitaly_image_tag) gitlab_shell_image_repository="${IMAGE_REPOSITORY}/gitlab-shell" - gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-ee" + gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-${GITLAB_IMAGE_SUFFIX}" sentry_enabled="false" if [ -n "${REVIEW_APPS_SENTRY_DSN}" ]; then @@ -394,7 +395,8 @@ function verify_deploy() { mkdir -p curl-logs/ - for i in {1..60}; do # try for 5 minutes + local max_try_times=${GITLAB_VERIFY_DEPLOY_TIMEOUT_MINUTES} * 60 / 5 + for i in {1..max_try_times}; do # try for GITLAB_VERIFY_DEPLOY_TIMEOUT_MINUTES minutes, default 5 minutes local now=$(date '+%H:%M:%S') echo "[${now}] Verifying deployment at ${CI_ENVIRONMENT_URL}/users/sign_in" log_name="curl-logs/${now}.log" diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index 3c39d8745a4..ad2fccc14bf 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' # Integration test that exports a file using the Import/Export feature # It looks up for any sensitive word inside the JSON, so if a sensitive word is found # we'll have to either include it adding the model that includes it to the +safe_list+ -# or make sure the attribute is blacklisted in the +import_export.yml+ configuration +# or make sure the attribute is denylisted in the +import_export.yml+ configuration RSpec.describe 'Import/Export - project export integration test', :js, feature_category: :importers do include ExportFileHelper diff --git a/spec/features/projects/integrations/user_activates_prometheus_spec.rb b/spec/features/projects/integrations/user_activates_prometheus_spec.rb index a47000672ca..db71256b294 100644 --- a/spec/features/projects/integrations/user_activates_prometheus_spec.rb +++ b/spec/features/projects/integrations/user_activates_prometheus_spec.rb @@ -13,7 +13,6 @@ RSpec.describe 'User activates Prometheus', feature_category: :integrations do it 'saves and activates integration', :js do visit_project_integration('Prometheus') check('Active') - fill_in('API URL', with: 'http://prometheus.example.com') click_button('Save changes') diff --git a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js index 4a0c7f65493..e6b38a1e824 100644 --- a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js +++ b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js @@ -68,8 +68,11 @@ describe('AlertsSettingsForm', () => { await options.at(index).setSelected(); }; - const enableIntegration = (index, value) => { - findFormFields().at(index).setValue(value); + const enableIntegration = (index, value = '') => { + if (value !== '') { + findFormFields().at(index).setValue(value); + } + findFormToggle().vm.$emit('change', true); }; @@ -100,7 +103,8 @@ describe('AlertsSettingsForm', () => { it('hides the name input when the selected value is prometheus', async () => { createComponent(); await selectOptionAtIndex(2); - expect(findFormFields().at(0).attributes('id')).not.toBe('name-integration'); + + expect(findFormFields()).toHaveLength(0); }); it('verify pricing link url', () => { @@ -203,8 +207,8 @@ describe('AlertsSettingsForm', () => { it('create', async () => { createComponent(); await selectOptionAtIndex(2); - const apiUrl = 'https://test.com'; - enableIntegration(0, apiUrl); + enableIntegration(0); + const submitBtn = findSubmitButton(); expect(submitBtn.exists()).toBe(true); expect(submitBtn.text()).toBe('Save integration'); @@ -213,14 +217,14 @@ describe('AlertsSettingsForm', () => { expect(wrapper.emitted('create-new-integration')[0][0]).toMatchObject({ type: typeSet.prometheus, - variables: { apiUrl, active: true }, + variables: { active: true }, }); }); it('update', () => { createComponent({ data: { - integrationForm: { id: '1', apiUrl: 'https://test-pre.com', type: typeSet.prometheus }, + integrationForm: { id: '1', type: typeSet.prometheus }, currentIntegration: { id: '1' }, }, props: { @@ -228,8 +232,7 @@ describe('AlertsSettingsForm', () => { }, }); - const apiUrl = 'https://test-post.com'; - enableIntegration(0, apiUrl); + enableIntegration(0); const submitBtn = findSubmitButton(); expect(submitBtn.exists()).toBe(true); @@ -239,7 +242,7 @@ describe('AlertsSettingsForm', () => { expect(wrapper.emitted('update-integration')[0][0]).toMatchObject({ type: typeSet.prometheus, - variables: { apiUrl, active: true }, + variables: { active: true }, }); }); }); @@ -442,16 +445,8 @@ describe('AlertsSettingsForm', () => { expect(findSubmitButton().attributes('disabled')).toBe(undefined); }); - it('should not be able to submit when Prometheus integration form is invalid', async () => { - await selectOptionAtIndex(2); - await findFormFields().at(0).vm.$emit('input', ''); - - expect(findSubmitButton().attributes('disabled')).toBeDefined(); - }); - it('should be able to submit when Prometheus integration form is valid', async () => { await selectOptionAtIndex(2); - await findFormFields().at(0).vm.$emit('input', 'http://valid.url'); expect(findSubmitButton().attributes('disabled')).toBe(undefined); }); diff --git a/spec/frontend/jobs/components/job/job_container_item_spec.js b/spec/frontend/jobs/components/job/job_container_item_spec.js index 8121aa1172f..9f5ef88c702 100644 --- a/spec/frontend/jobs/components/job/job_container_item_spec.js +++ b/spec/frontend/jobs/components/job/job_container_item_spec.js @@ -30,9 +30,7 @@ describe('JobContainerItem', () => { }); it('displays a status icon', () => { - const ciIcon = findCiIconComponent(); - - expect(ciIcon.props('status')).toBe(job.status); + expect(findCiIconComponent().props('status')).toBe(job.status); }); it('displays the job name', () => { diff --git a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js index 1928dbf72b6..fd2220a20d2 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js @@ -6,7 +6,6 @@ import { GlFormGroup, GlModal, GlSprintf, - GlEmptyState, } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -78,7 +77,6 @@ describe('DependencyProxyApp', () => { const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup); const findProxyCountText = () => wrapper.findByTestId('proxy-count'); const findManifestList = () => wrapper.findComponent(ManifestsList); - const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findClearCacheDropdownList = () => wrapper.findComponent(GlDropdown); const findClearCacheModal = () => wrapper.findComponent(GlModal); const findClearCacheAlert = () => wrapper.findComponent(GlAlert); @@ -120,11 +118,15 @@ describe('DependencyProxyApp', () => { expect(findFormGroup().attributes('label')).toBe( DependencyProxyApp.i18n.proxyImagePrefix, ); + expect(findFormGroup().attributes('labelfor')).toBe('proxy-url'); }); it('renders a form input group', () => { expect(findFormInputGroup().exists()).toBe(true); + expect(findFormInputGroup().attributes('id')).toBe('proxy-url'); expect(findFormInputGroup().props('value')).toBe(proxyData().dependencyProxyImagePrefix); + expect(findFormInputGroup().attributes('readonly')).toBeDefined(); + expect(findFormInputGroup().props('selectOnClick')).toBe(true); }); it('form input group has a clipboard button', () => { @@ -175,23 +177,12 @@ describe('DependencyProxyApp', () => { return waitForPromises(); }); - it('shows the empty state message', () => { - expect(findEmptyState().props()).toMatchObject({ - svgPath: provideDefaults.noManifestsIllustration, - title: DependencyProxyApp.i18n.noManifestTitle, - }); - }); - - it('hides the list', () => { - expect(findManifestList().exists()).toBe(false); + it('renders the list', () => { + expect(findManifestList().exists()).toBe(true); }); }); describe('when there are manifests', () => { - it('hides the empty state message', () => { - expect(findEmptyState().exists()).toBe(false); - }); - it('shows list', () => { expect(findManifestList().props()).toMatchObject({ dependencyProxyImagePrefix: proxyData().dependencyProxyImagePrefix, diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js index 4149f728cd8..8f445843aa8 100644 --- a/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js +++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifest_list_spec.js @@ -1,6 +1,7 @@ import { GlKeysetPagination, GlSkeletonLoader } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue'; +import ManifestsEmptyState from '~/packages_and_registries/dependency_proxy/components/manifests_empty_state.vue'; import Component from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue'; import { proxyData, @@ -24,6 +25,7 @@ describe('Manifests List', () => { }); }; + const findEmptyState = () => wrapper.findComponent(ManifestsEmptyState); const findRows = () => wrapper.findAllComponents(ManifestRow); const findPagination = () => wrapper.findComponent(GlKeysetPagination); const findMainArea = () => wrapper.findByTestId('main-area'); @@ -38,7 +40,13 @@ describe('Manifests List', () => { it('shows a row for every manifest', () => { createComponent(); - expect(findRows().length).toBe(defaultProps.manifests.length); + expect(findRows()).toHaveLength(defaultProps.manifests.length); + }); + + it('does not show the empty state component', () => { + createComponent(); + + expect(findEmptyState().exists()).toBe(false); }); it('binds a manifest to each row', () => { @@ -68,6 +76,20 @@ describe('Manifests List', () => { }); }); + describe('when there are no manifests', () => { + beforeEach(() => { + createComponent({ ...defaultProps, manifests: [], pagination: {} }); + }); + + it('shows the empty state component', () => { + expect(findEmptyState().exists()).toBe(true); + }); + + it('hides the list', () => { + expect(findRows()).toHaveLength(0); + }); + }); + describe('pagination', () => { it('is hidden when there is no next or prev pages', () => { createComponent({ ...defaultProps, pagination: {} }); diff --git a/spec/frontend/packages_and_registries/dependency_proxy/components/manifests_empty_state_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/components/manifests_empty_state_spec.js new file mode 100644 index 00000000000..00c1469994b --- /dev/null +++ b/spec/frontend/packages_and_registries/dependency_proxy/components/manifests_empty_state_spec.js @@ -0,0 +1,81 @@ +import { GlEmptyState, GlFormGroup, GlFormInputGroup, GlLink, GlSprintf } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ManifestsEmptyState from '~/packages_and_registries/dependency_proxy/components/manifests_empty_state.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; + +describe('manifests empty state', () => { + let wrapper; + + const provideDefaults = { + noManifestsIllustration: 'noManifestsIllustration', + }; + + const createComponent = ({ stubs = {} } = {}) => { + wrapper = shallowMountExtended(ManifestsEmptyState, { + provide: provideDefaults, + stubs: { + GlEmptyState, + GlFormInputGroup, + ...stubs, + }, + }); + }; + + const findDocsLink = () => wrapper.findComponent(GlLink); + const findEmptyTextDescription = () => wrapper.findAllComponents(GlSprintf).at(0); + const findDocumentationTextDescription = () => wrapper.findAllComponents(GlSprintf).at(1); + const findClipBoardButton = () => wrapper.findComponent(ClipboardButton); + const findFormGroup = () => wrapper.findComponent(GlFormGroup); + const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup); + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + + beforeEach(() => { + createComponent(); + }); + + it('shows the empty state message', () => { + expect(findEmptyState().props()).toMatchObject({ + svgPath: provideDefaults.noManifestsIllustration, + title: ManifestsEmptyState.i18n.noManifestTitle, + }); + }); + + it('renders correct description', () => { + expect(findEmptyTextDescription().attributes('message')).toBe( + ManifestsEmptyState.i18n.emptyText, + ); + expect(findDocumentationTextDescription().attributes('message')).toBe( + ManifestsEmptyState.i18n.documentationText, + ); + }); + + it('renders a form group with a label', () => { + expect(findFormGroup().attributes('label')).toBe(ManifestsEmptyState.i18n.codeExampleLabel); + expect(findFormGroup().attributes('label-sr-only')).toBeDefined(); + expect(findFormGroup().attributes('label-for')).toBe('code-example'); + }); + + it('renders a form input group', () => { + expect(findFormInputGroup().exists()).toBe(true); + expect(findFormInputGroup().attributes('id')).toBe('code-example'); + expect(findFormInputGroup().props('value')).toBe(ManifestsEmptyState.codeExample); + expect(findFormInputGroup().attributes('readonly')).toBeDefined(); + expect(findFormInputGroup().props('selectOnClick')).toBe(true); + }); + + it('form input group has a clipboard button', () => { + expect(findClipBoardButton().exists()).toBe(true); + expect(findClipBoardButton().props()).toMatchObject({ + text: ManifestsEmptyState.codeExample, + title: ManifestsEmptyState.i18n.copyExample, + }); + }); + + it('shows link to docs', () => { + createComponent({ stubs: { GlSprintf } }); + + expect(findDocsLink().attributes('href')).toBe( + ManifestsEmptyState.links.DEPENDENCY_PROXY_HELP_PAGE_PATH, + ); + }); +}); diff --git a/spec/frontend/pipelines/graph/job_name_component_spec.js b/spec/frontend/pipelines/graph/job_name_component_spec.js index ec432e98fdf..fca4c43d9fa 100644 --- a/spec/frontend/pipelines/graph/job_name_component_spec.js +++ b/spec/frontend/pipelines/graph/job_name_component_spec.js @@ -1,6 +1,6 @@ import { mount } from '@vue/test-utils'; import jobNameComponent from '~/pipelines/components/jobs_shared/job_name_component.vue'; -import ciIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; describe('job name component', () => { let wrapper; @@ -24,7 +24,7 @@ describe('job name component', () => { }); it('should render an icon with the provided status', () => { - expect(wrapper.findComponent(ciIcon).exists()).toBe(true); + expect(wrapper.findComponent(CiIcon).exists()).toBe(true); expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true); }); }); diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js index bf92cd585d9..8dae2aac664 100644 --- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js @@ -10,7 +10,7 @@ import { ACTION_FAILURE, UPSTREAM, DOWNSTREAM } from '~/pipelines/components/gra import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue'; import CancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql'; import RetryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql'; -import CiStatus from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; import mockPipeline from './linked_pipelines_mock_data'; describe('Linked pipeline', () => { @@ -87,7 +87,7 @@ describe('Linked pipeline', () => { }); it('should render an svg within the status container', () => { - const pipelineStatusElement = wrapper.findComponent(CiStatus); + const pipelineStatusElement = wrapper.findComponent(CiIcon); expect(pipelineStatusElement.find('svg').exists()).toBe(true); }); @@ -97,7 +97,7 @@ describe('Linked pipeline', () => { }); it('should have a ci-status child component', () => { - expect(wrapper.findComponent(CiStatus).exists()).toBe(true); + expect(wrapper.findComponent(CiIcon).exists()).toBe(true); }); it('should render the pipeline id', () => { diff --git a/spec/frontend/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon_spec.js index 31d63654168..295b91fda81 100644 --- a/spec/frontend/vue_shared/components/ci_icon_spec.js +++ b/spec/frontend/vue_shared/components/ci_icon_spec.js @@ -1,6 +1,6 @@ import { GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import ciIcon from '~/vue_shared/components/ci_icon.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; describe('CI Icon component', () => { let wrapper; @@ -8,7 +8,7 @@ describe('CI Icon component', () => { const findIconWrapper = () => wrapper.find('[data-testid="ci-icon-wrapper"]'); it('should render a span element with an svg', () => { - wrapper = shallowMount(ciIcon, { + wrapper = shallowMount(CiIcon, { propsData: { status: { icon: 'status_success', @@ -26,7 +26,7 @@ describe('CI Icon component', () => { ${true} | ${'active'} ${false} | ${'active'} `('active should be $isActive', ({ isActive, cssClass }) => { - wrapper = shallowMount(ciIcon, { + wrapper = shallowMount(CiIcon, { propsData: { status: { icon: 'status_success', @@ -49,7 +49,7 @@ describe('CI Icon component', () => { ${true} | ${'interactive'} ${false} | ${'interactive'} `('interactive should be $isInteractive', ({ isInteractive, cssClass }) => { - wrapper = shallowMount(ciIcon, { + wrapper = shallowMount(CiIcon, { propsData: { status: { icon: 'status_success', @@ -79,7 +79,7 @@ describe('CI Icon component', () => { ${'status_canceled'} | ${'canceled'} | ${'ci-status-icon-canceled'} ${'status_manual'} | ${'manual'} | ${'ci-status-icon-manual'} `('should render a $group status', ({ icon, group, cssClass }) => { - wrapper = shallowMount(ciIcon, { + wrapper = shallowMount(CiIcon, { propsData: { status: { icon, diff --git a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb index e1135f4d546..4b2bb6cbb02 100644 --- a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb +++ b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFile do +RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFile, feature_category: :mlops do include RepoHelpers let_it_be(:project) { create(:project, :repository) } diff --git a/spec/lib/gitlab/import_export/references_configuration_spec.rb b/spec/lib/gitlab/import_export/references_configuration_spec.rb index 84c5b564cb1..a9765a8747d 100644 --- a/spec/lib/gitlab/import_export/references_configuration_spec.rb +++ b/spec/lib/gitlab/import_export/references_configuration_spec.rb @@ -6,7 +6,7 @@ require 'spec_helper' # Checks whether there are new reference attributes ending with _id in models that are currently being exported as part of the # project Import/Export feature. # If there are new references (foreign keys), these will have to either be replaced with actual relation -# or to be blacklisted by using the import_export.yml configuration file. +# or to be denylisted by using the import_export.yml configuration file. # Likewise, new models added to import_export.yml, will need to be added with their correspondent relations # to this spec. RSpec.describe 'Import/Export Project configuration', feature_category: :importers do diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb index 2246272d3af..e33c60ef592 100644 --- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb +++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb @@ -147,7 +147,7 @@ RSpec.describe Gitlab::MarkdownCache::ActiveRecord::Extension do end describe '.attributes' do - it 'excludes cache attributes that is blacklisted by default' do + it 'excludes cache attributes that are denylisted by default' do expect(thing.attributes.keys.sort).not_to include(%w[description_html]) end end diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 83d4d3fb612..a3835f9eed0 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -136,7 +136,7 @@ RSpec.describe Gitlab::Middleware::Go, feature_category: :source_code_management it_behaves_like 'unauthorized' end - context 'with a blacklisted ip' do + context 'with a denylisted ip' do it 'returns forbidden' do expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::IpBlocked) response = go diff --git a/spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb b/spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb index 0a71699971e..842020896d9 100644 --- a/spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb +++ b/spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb @@ -16,6 +16,7 @@ RSpec.describe VulnerabilityFindingSignatureHelpers do describe '#priority' do it 'returns numeric values of the priority string' do + expect(cls.new('scope_offset_compressed').priority).to eq(4) expect(cls.new('scope_offset').priority).to eq(3) expect(cls.new('location').priority).to eq(2) expect(cls.new('hash').priority).to eq(1) @@ -24,6 +25,7 @@ RSpec.describe VulnerabilityFindingSignatureHelpers do describe '#self.priority' do it 'returns the numeric value of the provided string' do + expect(cls.priority('scope_offset_compressed')).to eq(4) expect(cls.priority('scope_offset')).to eq(3) expect(cls.priority('location')).to eq(2) expect(cls.priority('hash')).to eq(1) diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb index 5344a2c2bb7..21cf8ab2c79 100644 --- a/spec/requests/admin/users_controller_spec.rb +++ b/spec/requests/admin/users_controller_spec.rb @@ -6,12 +6,12 @@ RSpec.describe Admin::UsersController, :enable_admin_mode, feature_category: :us let_it_be(:admin) { create(:admin) } let_it_be(:user) { create(:user) } + before do + sign_in(admin) + end + describe 'PUT #block' do context 'when request format is :json' do - before do - sign_in(admin) - end - subject(:request) { put block_admin_user_path(user, format: :json) } context 'when user was blocked' do @@ -39,4 +39,16 @@ RSpec.describe Admin::UsersController, :enable_admin_mode, feature_category: :us end end end + + describe 'PUT #unlock' do + before do + user.lock_access! + end + + subject(:request) { put unlock_admin_user_path(user) } + + it 'unlocks the user' do + expect { request }.to change { user.reload.access_locked? }.from(true).to(false) + end + end end diff --git a/vendor/gems/ipynbdiff/.gitlab-ci.yml b/vendor/gems/ipynbdiff/.gitlab-ci.yml deleted file mode 100644 index dd8f61050bb..00000000000 --- a/vendor/gems/ipynbdiff/.gitlab-ci.yml +++ /dev/null @@ -1,10 +0,0 @@ -include: - - local: gems/gem.gitlab-ci.yml - inputs: - gem_name: "ipynbdiff" - gem_path_prefix: "vendor/gems/" - -rspec: - parallel: - matrix: - - RUBY_VERSION: ["3.0", "3.1"] # 3.2 isn't supported yet diff --git a/vendor/gems/ipynbdiff/spec/symbol_map_spec.rb b/vendor/gems/ipynbdiff/spec/symbol_map_spec.rb deleted file mode 100644 index 5fba47c85af..00000000000 --- a/vendor/gems/ipynbdiff/spec/symbol_map_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -require 'rspec' -require 'json' -require 'rspec-parameterized' -require 'symbol_map' - -describe IpynbDiff::SymbolMap do - def res(*cases) - cases&.to_h || [] - end - - describe '#parse' do - subject { IpynbDiff::SymbolMap.parse(JSON.pretty_generate(source)) } - - context 'Object with blank key' do - let(:source) { { "": { "": 5 } }} - - it { is_expected.to match_array(res([".", 2], ["..", 3])) } - end - - context 'Empty object' do - let(:source) { {} } - - it { is_expected.to be_empty } - end - - context 'Empty array' do - let(:source) { [] } - - it { is_expected.to be_empty } - end - - context 'Object with inner object and number' do - let(:source) { { obj1: { obj2: 1 } } } - - it { is_expected.to match_array(res( ['.obj1', 2], ['.obj1.obj2', 3])) } - end - - context 'Object with inner object and number, string and array with object' do - let(:source) { { obj1: { obj2: [123, 2, true], obj3: "hel\nlo", obj4: true, obj5: 123, obj6: 'a' } } } - - it do - is_expected.to match_array( - res(['.obj1', 2], - ['.obj1.obj2', 3], - ['.obj1.obj2.0', 4], - ['.obj1.obj2.1', 5], - ['.obj1.obj2.2', 6], - ['.obj1.obj3', 8], - ['.obj1.obj4', 9], - ['.obj1.obj5', 10], - ['.obj1.obj6', 11]) - ) - end - end - end -end diff --git a/vendor/gems/ipynbdiff/spec/test_helper.rb b/vendor/gems/ipynbdiff/spec/test_helper.rb deleted file mode 100644 index f9c416885a1..00000000000 --- a/vendor/gems/ipynbdiff/spec/test_helper.rb +++ /dev/null @@ -1,23 +0,0 @@ -BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'testdata') - -FROM_PATH = File.join(BASE_PATH, 'from.ipynb') -TO_PATH = File.join(BASE_PATH, 'to.ipynb') - -FROM_IPYNB = File.read(FROM_PATH) -TO_IPYNB = File.read(TO_PATH) - -def input_for_test(test_case) - File.join(BASE_PATH, test_case, 'input.ipynb') -end - -def expected_symbols(test_case) - File.join(BASE_PATH, test_case, 'expected_symbols.txt') -end - -def expected_md(test_case) - File.join(BASE_PATH, test_case, 'expected.md') -end - -def expected_line_numbers(test_case) - File.join(BASE_PATH, test_case, 'expected_line_numbers.txt') -end diff --git a/vendor/gems/ipynbdiff/spec/transformer_spec.rb b/vendor/gems/ipynbdiff/spec/transformer_spec.rb deleted file mode 100644 index 660d0a2bd79..00000000000 --- a/vendor/gems/ipynbdiff/spec/transformer_spec.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -require 'rspec' -require 'ipynbdiff' -require 'json' -require 'rspec-parameterized' - -TRANSFORMER_BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'testdata') - -def read_file(*paths) - File.read(File.join(TRANSFORMER_BASE_PATH, *paths)) -end - -def default_config - @default_config ||= { - include_frontmatter: false, - hide_images: false - } -end - -def from_ipynb - @from_ipynb ||= read_file('from.ipynb') -end - -def read_notebook(input_path) - read_file(input_path, 'input.ipynb') -rescue Errno::ENOENT - from_ipynb -end - -describe IpynbDiff::Transformer do - describe 'When notebook is valid' do - using RSpec::Parameterized::TableSyntax - - where(:ctx, :test_case, :config) do - 'renders metadata' | 'no_cells' | { include_frontmatter: true } - 'is empty for no cells, but metadata is false' | 'no_cells_no_metadata' | {} - 'adds markdown cell' | 'only_md' | {} - 'adds block with only one line of markdown' | 'single_line_md' | {} - 'adds raw block' | 'only_raw' | {} - 'code cell, but no output' | 'only_code' | {} - 'code cell, but no language' | 'only_code_no_language' | {} - 'code cell, but no kernelspec' | 'only_code_no_kernelspec' | {} - 'code cell, but no nb metadata' | 'only_code_no_metadata' | {} - 'text output' | 'text_output' | {} - 'ignores html output' | 'ignore_html_output' | {} - 'extracts png output along with text' | 'text_png_output' | {} - 'embeds svg as image' | 'svg' | {} - 'extracts latex output' | 'latex_output' | {} - 'extracts error output' | 'error_output' | {} - 'does not fetch tags if there is no cell metadata' | 'no_metadata_on_cell' | {} - 'generates :percent decorator' | 'percent_decorator' | {} - 'parses stream output' | 'stream_text' | {} - 'ignores unknown output type' | 'unknown_output_type' | {} - 'handles backslash correctly' | 'backslash_as_last_char' | {} - 'multiline png output' | 'multiline_png_output' | {} - 'hides images when option passed' | 'hide_images' | { hide_images: true } - '\n within source lines' | 'source_with_linebreak' | { hide_images: true } - end - - with_them do - let(:expected_md) { read_file(test_case, 'expected.md') } - let(:expected_symbols) { read_file(test_case, 'expected_symbols.txt') } - let(:input) { read_notebook(test_case) } - let(:transformed) { IpynbDiff::Transformer.new(**default_config.merge(config)).transform(input) } - - it 'generates the expected markdown' do - expect(transformed.as_text).to eq expected_md - end - - it 'marks the lines correctly' do - blocks = transformed.blocks.map { |b| b[:source_symbol] }.join("\n") - result = expected_symbols - - expect(blocks).to eq result - end - end - end - - it 'generates the correct transformed to source line map' do - input = read_file('text_png_output', 'input.ipynb' ) - expected_line_numbers = read_file('text_png_output', 'expected_line_numbers.txt' ) - - transformed = IpynbDiff::Transformer.new(**{ include_frontmatter: false }).transform(input) - - line_numbers = transformed.blocks.map { |b| b[:source_line] }.join("\n") - - expect(line_numbers).to eq(expected_line_numbers) - - end - - context 'When the notebook is invalid' do - [ - ['because the json is invalid', 'a'], - ['because it doesnt have the cell tag', '{"metadata":[]}'] - ].each do |ctx, notebook| - context ctx do - it 'raises error' do - expect do - IpynbDiff::Transformer.new.transform(notebook) - end.to raise_error(IpynbDiff::InvalidNotebookError) - end - end - end - - context 'when notebook can not be parsed' do - it 'raises error' do - notebook = '{"cells":[]}' - allow(Oj::Parser.usual).to receive(:parse).and_return(nil) - - expect do - IpynbDiff::Transformer.new.transform(notebook) - end.to raise_error(IpynbDiff::InvalidNotebookError) - end - end - end -end |