diff options
45 files changed, 358 insertions, 86 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b9b217126b..54f1e859e91 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -122,7 +122,7 @@ workflow: variables: PG_VERSION: "13" - DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-16.14-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36" + DEFAULT_CI_IMAGE: "${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}.patched-golang-${GO_VERSION}-rust-${RUST_VERSION}-node-18.16-postgresql-${PG_VERSION}:rubygems-${RUBYGEMS_VERSION}-git-2.36-lfs-2.9-chrome-${CHROME_VERSION}-yarn-1.22-graphicsmagick-1.3.36" # We set $GITLAB_DEPENDENCY_PROXY to another variable (since it's set at the group level and has higher precedence than .gitlab-ci.yml) # so that we can override $GITLAB_DEPENDENCY_PROXY_ADDRESS in workflow rules. GITLAB_DEPENDENCY_PROXY_ADDRESS: "${GITLAB_DEPENDENCY_PROXY}" diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 708069d9b61..b8a30ef807b 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -3,7 +3,7 @@ - .default-retry - .default-before_script - .assets-compile-cache - image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-node-16.14:rubygems-${RUBYGEMS_VERSION}-git-2.33-lfs-2.9-yarn-1.22-graphicsmagick-1.3.36 + image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-${DEBIAN_VERSION}-ruby-${RUBY_VERSION}-node-18.16:rubygems-${RUBYGEMS_VERSION}-git-2.33-lfs-2.9-yarn-1.22-graphicsmagick-1.3.36 variables: SETUP_DB: "false" WEBPACK_VENDOR_DLL: "true" @@ -1 +1 @@ -16.15.0 +18.16.0 diff --git a/app/assets/javascripts/analytics/cycle_analytics/components/filter_bar.vue b/app/assets/javascripts/analytics/cycle_analytics/components/filter_bar.vue index 133513d6c21..0d7589b04b1 100644 --- a/app/assets/javascripts/analytics/cycle_analytics/components/filter_bar.vue +++ b/app/assets/javascripts/analytics/cycle_analytics/components/filter_bar.vue @@ -22,6 +22,7 @@ import UserToken from '~/vue_shared/components/filtered_search_bar/tokens/user_t import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; import UrlSync from '~/vue_shared/components/url_sync.vue'; +import { MAX_LABELS } from '../constants'; export default { name: 'FilterBar', @@ -70,6 +71,7 @@ export default { symbol: '~', operators: OPERATORS_IS, fetchLabels: this.fetchLabels, + maxSuggestions: MAX_LABELS, }, { icon: 'pencil', diff --git a/app/assets/javascripts/analytics/cycle_analytics/constants.js b/app/assets/javascripts/analytics/cycle_analytics/constants.js index bea562fb18c..c14f3cfc6c9 100644 --- a/app/assets/javascripts/analytics/cycle_analytics/constants.js +++ b/app/assets/javascripts/analytics/cycle_analytics/constants.js @@ -43,3 +43,4 @@ export const METRICS_REQUESTS = [ export const MILESTONES_ENDPOINT = '/-/milestones.json'; export const LABELS_ENDPOINT = '/-/labels.json'; +export const MAX_LABELS = 100; diff --git a/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue b/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue index fe19977f783..6fd4edf5847 100644 --- a/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue +++ b/app/assets/javascripts/ci/runner/components/registration/registration_feedback_banner.vue @@ -1,5 +1,5 @@ <script> -import ILLUSTRATION_URL from '@gitlab/svgs/dist/illustrations/multi-editor_all_changes_committed_empty.svg?url'; +import ILLUSTRATION_URL from '@gitlab/svgs/dist/illustrations/rocket-launch-md.svg?url'; import { GlBanner } from '@gitlab/ui'; import { s__ } from '~/locale'; import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; diff --git a/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue b/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue index c30a824120d..4e68c2ea71a 100644 --- a/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue +++ b/app/assets/javascripts/ci/runner/components/runner_jobs_empty_state.vue @@ -1,5 +1,5 @@ <script> -import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/pipelines_empty.svg?url'; +import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-pipeline-md.svg?url'; import { GlEmptyState } from '@gitlab/ui'; import { s__ } from '~/locale'; @@ -19,7 +19,11 @@ export default { </script> <template> - <gl-empty-state :svg-path="$options.EMPTY_STATE_SVG_URL" :title="$options.i18n.title"> + <gl-empty-state + :svg-path="$options.EMPTY_STATE_SVG_URL" + :svg-height="150" + :title="$options.i18n.title" + > <template #description> <p>{{ $options.i18n.description }}</p> </template> diff --git a/app/assets/javascripts/ci/runner/components/runner_list_empty_state.vue b/app/assets/javascripts/ci/runner/components/runner_list_empty_state.vue index 8606c22db34..ab2dc1b8ba3 100644 --- a/app/assets/javascripts/ci/runner/components/runner_list_empty_state.vue +++ b/app/assets/javascripts/ci/runner/components/runner_list_empty_state.vue @@ -1,6 +1,6 @@ <script> -import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/pipelines_empty.svg?url'; -import FILTERED_SVG_URL from '@gitlab/svgs/dist/illustrations/magnifying-glass.svg?url'; +import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-pipeline-md.svg?url'; +import FILTERED_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.svg?url'; import { GlEmptyState, GlLink, GlSprintf, GlModalDirective } from '@gitlab/ui'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; diff --git a/app/assets/javascripts/graphql_shared/issuable_client.js b/app/assets/javascripts/graphql_shared/issuable_client.js index 9d2c406d7b7..74eb124535e 100644 --- a/app/assets/javascripts/graphql_shared/issuable_client.js +++ b/app/assets/javascripts/graphql_shared/issuable_client.js @@ -92,14 +92,6 @@ export const config = { }); }, }, - userPermissions: { - read(permission = {}) { - return { - ...permission, - setWorkItemMetadata: false, - }; - }, - }, }, }, MemberInterfaceConnection: { diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue index b5783265ffa..e2829d75ab1 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue @@ -113,13 +113,15 @@ export default { * present in "Recently used" */ availableSuggestions() { - return this.searchKey + const suggestions = this.searchKey ? this.suggestions : this.suggestions.filter( (tokenValue) => !this.recentTokenIds.includes(tokenValue[this.valueIdentifier]) && !this.preloadedTokenIds.includes(tokenValue[this.valueIdentifier]), ); + + return this.applyMaxSuggestions(suggestions); }, showDefaultSuggestions() { return this.availableDefaultSuggestions.length > 0; @@ -196,6 +198,12 @@ export default { setTokenValueToRecentlyUsed(this.config.recentSuggestionsStorageKey, activeTokenValue); } }, + applyMaxSuggestions(suggestions) { + const { maxSuggestions } = this.config; + if (!maxSuggestions || maxSuggestions <= 0) return suggestions; + + return suggestions.slice(0, maxSuggestions); + }, }, }; </script> diff --git a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql index b045796579b..02243baf28d 100644 --- a/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item.fragment.graphql @@ -27,8 +27,8 @@ fragment WorkItem on WorkItem { userPermissions { deleteWorkItem updateWorkItem - setWorkItemMetadata @client adminParentLink + setWorkItemMetadata } widgets { ...WorkItemWidgets diff --git a/app/controllers/concerns/skips_already_signed_in_message.rb b/app/controllers/concerns/skips_already_signed_in_message.rb new file mode 100644 index 00000000000..7630cf4f4e1 --- /dev/null +++ b/app/controllers/concerns/skips_already_signed_in_message.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# This concern can be included in devise controllers to skip showing an "already signed in" +# warning on registrations and logins +module SkipsAlreadySignedInMessage + extend ActiveSupport::Concern + + included do + # replaced with :require_no_authentication_without_flash + # rubocop: disable Rails/LexicallyScopedActionFilter + # The actions are defined in Devise + skip_before_action :require_no_authentication, only: [:new, :create] + before_action :require_no_authentication_without_flash, only: [:new, :create] + # rubocop: enable Rails/LexicallyScopedActionFilter + end + + def require_no_authentication_without_flash + require_no_authentication + + return unless flash[:alert] == I18n.t('devise.failure.already_authenticated') + + flash[:alert] = nil + end +end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 3e6683fc867..f481681da02 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -10,6 +10,7 @@ class RegistrationsController < Devise::RegistrationsController include GoogleAnalyticsCSP include PreferredLanguageSwitcher include Gitlab::Tracking::Helpers::WeakPasswordErrorEvent + include SkipsAlreadySignedInMessage layout 'devise' diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 8a79353f490..a9972cbd885 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -14,13 +14,11 @@ class SessionsController < Devise::SessionsController include VerifiesWithEmail include GoogleAnalyticsCSP include PreferredLanguageSwitcher + include SkipsAlreadySignedInMessage skip_before_action :check_two_factor_requirement, only: [:destroy] skip_before_action :check_password_expiration, only: [:destroy] - # replaced with :require_no_authentication_without_flash - skip_before_action :require_no_authentication, only: [:new, :create] - prepend_before_action :check_initial_setup, only: [:new] prepend_before_action :authenticate_with_two_factor, if: -> { action_name == 'create' && two_factor_enabled? } @@ -29,7 +27,6 @@ class SessionsController < Devise::SessionsController prepend_before_action :require_no_authentication_without_flash, only: [:new, :create] prepend_before_action :check_forbidden_password_based_login, if: -> { action_name == 'create' && password_based_login? } prepend_before_action :ensure_password_authentication_enabled!, if: -> { action_name == 'create' && password_based_login? } - before_action :auto_sign_in_with_provider, only: [:new] before_action :init_preferred_language, only: :new before_action :store_unauthenticated_sessions, only: [:new] @@ -96,14 +93,6 @@ class SessionsController < Devise::SessionsController private - def require_no_authentication_without_flash - require_no_authentication - - if flash[:alert] == I18n.t('devise.failure.already_authenticated') - flash[:alert] = nil - end - end - def captcha_enabled? request.headers[CAPTCHA_HEADER] && helpers.recaptcha_enabled? end diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb index 897367efdb3..b222ca5538d 100644 --- a/app/helpers/ci/pipelines_helper.rb +++ b/app/helpers/ci/pipelines_helper.rb @@ -93,7 +93,7 @@ module Ci pipeline_schedule_url: pipeline_schedules_path(project), empty_state_svg_path: image_path('illustrations/empty-state/empty-pipeline-md.svg'), error_state_svg_path: image_path('illustrations/pipelines_failed.svg'), - no_pipelines_svg_path: image_path('illustrations/pipelines_pending.svg'), + no_pipelines_svg_path: image_path('illustrations/empty-state/empty-pipeline-md.svg'), can_create_pipeline: can?(current_user, :create_pipeline, project).to_s, new_pipeline_path: can?(current_user, :create_pipeline, project) && new_project_pipeline_path(project), ci_lint_path: can?(current_user, :create_pipeline, project) && project_ci_lint_path(project), diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index 448909543c4..22b6919dcdc 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -42,7 +42,7 @@ module IdeHelper { 'empty-state-svg-path' => image_path('illustrations/multi_file_editor_empty.svg'), 'no-changes-state-svg-path' => image_path('illustrations/multi-editor_no_changes_empty.svg'), - 'committed-state-svg-path' => image_path('illustrations/multi-editor_all_changes_committed_empty.svg'), + 'committed-state-svg-path' => image_path('illustrations/rocket-launch-md.svg'), 'pipelines-empty-state-svg-path': image_path('illustrations/empty-state/empty-pipeline-md.svg'), 'switch-editor-svg-path': image_path('illustrations/rocket-launch-md.svg'), 'promotion-svg-path': image_path('illustrations/web-ide_promotion.svg'), diff --git a/app/models/group.rb b/app/models/group.rb index 1c73dc0f5b2..844b7496913 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -187,6 +187,8 @@ class Group < Namespace Group.from_union([by_id(ids), by_id(ids_by_full_path), where('LOWER(path) IN (?)', paths.map(&:downcase))]) end + scope :excluding_groups, ->(groups) { where.not(id: groups) } + scope :for_authorized_group_members, -> (user_ids) do joins(:group_members) .where(members: { user_id: user_ids }) diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index 70a2476c8e5..6049d1cc110 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -48,7 +48,7 @@ variant: :success) do |c| - c.with_body do - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/profile/index', anchor: 'add-details-to-your-profile-with-a-readme') } - = html_escape(_('%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more.%{help_link_end}')) % { project_path: "<strong>#{current_user.username} / #{current_user.username}</strong>".html_safe, help_link_start: help_link_start, help_link_end: '</a>'.html_safe } + = html_escape(_('%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more%{help_link_end}.')) % { project_path: "<strong>#{current_user.username} / #{current_user.username}</strong>".html_safe, help_link_start: help_link_start, help_link_end: '</a>'.html_safe } - if include_description .form-group diff --git a/app/views/projects/pages/_waiting.html.haml b/app/views/projects/pages/_waiting.html.haml index e8acadbabe3..0613ffc4809 100644 --- a/app/views/projects/pages/_waiting.html.haml +++ b/app/views/projects/pages/_waiting.html.haml @@ -1,7 +1,7 @@ .empty-state .row.gl-align-items-center.gl-justify-content-center .order-md-2 - = image_tag 'illustrations/pipelines_pending.svg' + = image_tag 'illustrations/empty-state/empty-pipeline-md.svg' .row.gl-align-items-center.gl-justify-content-center .text-content.gl-text-center.order-md-1 %h4= s_("GitLabPages|Waiting for the Pages Pipeline to complete...") diff --git a/db/post_migrate/20230518121320_remove_time_format_in_24h_column.rb b/db/post_migrate/20230518121320_remove_time_format_in_24h_column.rb new file mode 100644 index 00000000000..60abf4b6d8b --- /dev/null +++ b/db/post_migrate/20230518121320_remove_time_format_in_24h_column.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class RemoveTimeFormatIn24hColumn < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def up + remove_column :user_preferences, :time_format_in_24h + end + + def down + # rubocop:disable Migration/SchemaAdditionMethodsNoPost + add_column :user_preferences, :time_format_in_24h, :boolean + # rubocop:enable Migration/SchemaAdditionMethodsNoPost + end +end diff --git a/db/schema_migrations/20230518121320 b/db/schema_migrations/20230518121320 new file mode 100644 index 00000000000..333985c9b5e --- /dev/null +++ b/db/schema_migrations/20230518121320 @@ -0,0 +1 @@ +34f1874ef8bab4bee142d12e78c84697e8889e6fc6d5b4dfbcdc6e7ed16861b5
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index d1132f0066f..18ca4d27da1 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -23548,7 +23548,6 @@ CREATE TABLE user_preferences ( first_day_of_week integer, timezone character varying, time_display_relative boolean, - time_format_in_24h boolean, projects_sort character varying(64), show_whitespace_in_diffs boolean DEFAULT true NOT NULL, sourcegraph_enabled boolean, diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md index 4a70786b6ee..568acb76e5f 100644 --- a/doc/api/api_resources.md +++ b/doc/api/api_resources.md @@ -56,6 +56,7 @@ The following API resources are available in the project context: | [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) | | [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) | | [Iterations](iterations.md) **(PREMIUM)** | `/projects/:id/iterations` (also available for groups) | +| [Project CI/CD job token scope](project_job_token_scopes.md) | `/projects/:id/job_token_scope` | | [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` | | [Jobs Artifacts](job_artifacts.md) | `/projects/:id/jobs/:job_id/artifacts` | | [Labels](labels.md) | `/projects/:id/labels` | diff --git a/doc/api/project_job_token_scopes.md b/doc/api/project_job_token_scopes.md index 9120428998a..04dc3291bc4 100644 --- a/doc/api/project_job_token_scopes.md +++ b/doc/api/project_job_token_scopes.md @@ -4,35 +4,42 @@ group: Pipeline Security info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments" --- -# Project job token scope API **(FREE)** +# Project CI/CD job token scope API **(FREE)** + +You can read more about the [CI/CD job token](../ci/jobs/ci_job_token.md) NOTE: -- Every calls to the project token scope API must be authenticated, for example, with a personal access token. -- The authenticated user (personal access token) needs to have at least Maintainer role for the project. -- Depending on the usage, the personal access token requires read access (scope `read_api`) or read/write access (scope `api`) to the API. +All requests to the CI/CD job token scope API endpoint must be [authenticated](rest/index.md#authentication), and the authenticated user must have at least the Maintainer role for the project. -## Get a project job token scope +## Get a project's CI/CD job token access settings -Fetch CI_JOB_TOKEN access settings (job token scope) of a project. +Fetch the [CI/CD job token access settings](../ci/jobs/ci_job_token.md#configure-cicd-job-token-access) (job token scope) of a project. ```plaintext GET /projects/:id/job_token_scope ``` -Parameters +Supported attributes: | Attribute | Type | Required | Description | |-----------|----------------|------------------------|-------------| | `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. | -Example of request +If successful, returns [`200`](rest/index.md#status-codes) and the following response attributes: + +| Attribute | Type | Description | +|:-------------------|:---------|:----------------------| +| `inbound_enabled` | boolean | Indicates if the CI/CD job token generated in other projects has access to this project. | +| `outbound_enabled` | boolean | Indicates if the CI/CD job token generated in this project has access to other projects. [Deprecated and planned for removal in GitLab 17.0 .](../update/removals.md#limit-ci_job_token-scope-is-disabled) | + +Example request: ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/job_token_scope" ``` -Example of response +Example response: ```json { @@ -41,22 +48,24 @@ Example of response } ``` -## Patch a project job token scope +## Patch a project's CI/CD job token access settings -Patch CI_JOB_TOKEN access settings of a project. +Patch the [**Allow access to this project with a CI_JOB_TOKEN** setting](../ci/jobs/ci_job_token.md#disable-the-job-token-scope-allowlist) (job token scope) of a project. ```plaintext PATCH /projects/:id/job_token_scope ``` -Parameters +Supported attributes: | Attribute | Type | Required | Description | |-----------|----------------|-------------------------|-------------| | `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. | -| `enabled` | boolean | **{dotted-circle}** Yes | Indicates CI/CD job tokens generated in other projects have restricted access to this project. | +| `enabled` | boolean | **{check-circle}** Yes | Indicates CI/CD job tokens generated in other projects have restricted access to this project. | -Example of request +If successful, returns [`204`](rest/index.md#status-codes) and no response body. + +Example request: ```shell curl --request PATCH \ @@ -66,6 +75,69 @@ curl --request PATCH \ --data '{ "enabled": false }' ``` -Example of response +## Get a project's CI/CD job token inbound allowlist + +Fetch the [CI/CD job token inbound allowlist](../ci/jobs/ci_job_token.md#allow-access-to-your-project-with-a-job-token) (job token scope) of a project. + +```plaintext +GET /projects/:id/job_token_scope/allowlist +``` + +Supported attributes: + +| Attribute | Type | Required | Description | +|-----------|----------------|------------------------|-------------| +| `id` | integer/string | **{check-circle}** Yes | ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. | + +This endpoint supports [offset-based pagination](rest/index.md#offset-based-pagination). + +If successful, returns [`200`](rest/index.md#status-codes) and a list of project with limited fields for each project. + +Example request: + +```shell +curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/job_token_scope/allowlist" +``` + +Example response: -There is no response body. +```json +[ + { + "id": 4, + "description": null, + "name": "Diaspora Client", + "name_with_namespace": "Diaspora / Diaspora Client", + "path": "diaspora-client", + "path_with_namespace": "diaspora/diaspora-client", + "created_at": "2013-09-30T13:46:02Z", + "default_branch": "main", + "tag_list": [ + "example", + "disapora client" + ], + "topics": [ + "example", + "disapora client" + ], + "ssh_url_to_repo": "git@gitlab.example.com:diaspora/diaspora-client.git", + "http_url_to_repo": "https://gitlab.example.com/diaspora/diaspora-client.git", + "web_url": "https://gitlab.example.com/diaspora/diaspora-client", + "avatar_url": "https://gitlab.example.com/uploads/project/avatar/4/uploads/avatar.png", + "star_count": 0, + "last_activity_at": "2013-09-30T13:46:02Z", + "namespace": { + "id": 2, + "name": "Diaspora", + "path": "diaspora", + "kind": "group", + "full_path": "diaspora", + "parent_id": null, + "avatar_url": null, + "web_url": "https://gitlab.example.com/diaspora" + } + }, + { + ... + } +``` diff --git a/doc/install/installation.md b/doc/install/installation.md index 28aa37f0d1b..e4b4c6361a5 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -51,7 +51,7 @@ If the highest number stable branch is unclear, check the [GitLab blog](https:// | [RubyGems](#3-rubygems) | `3.4.x` | A specific RubyGems version is not fully needed, but it's recommended to update so you can enjoy some known performance improvements. | | [Go](#4-go) | `1.18.x` | From GitLab 15.6, Go 1.18 or later is required. | | [Git](#git) | `2.38.x` | From GitLab 15.8, Git 2.38.x and later is required. It's highly recommended that you use the [Git version provided by Gitaly](#git). | -| [Node.js](#5-node) | `16.15.0` | From GitLab 15.7, Node.js 16.15.0 or later is required. | +| [Node.js](#5-node) | `18.16.x` | From GitLab 16.1, Node.js 18.16 or later is required. | ## GitLab directory structure @@ -262,8 +262,8 @@ GitLab requires the use of Node to compile JavaScript assets, and Yarn to manage JavaScript dependencies. The current minimum requirements for these are: -- `node` 16.x releases (v16.15.0 or later). - [Other LTS versions of Node.js](https://github.com/nodejs/release#release-schedule) might be able to build assets, but we only guarantee Node.js 16.x. +- `node` 18.x releases (v18.16.0 or later). + [Other LTS versions of Node.js](https://github.com/nodejs/release#release-schedule) might be able to build assets, but we only guarantee Node.js 18.x. - `yarn` = v1.22.x (Yarn 2 is not supported yet) In many distributions, @@ -271,8 +271,8 @@ the versions provided by the official package repositories are out of date, so we must install through the following commands: ```shell -# install node v16.x -curl --location "https://deb.nodesource.com/setup_16.x" | sudo bash - +# install node v18.x +curl --location "https://deb.nodesource.com/setup_18.x" | sudo bash - sudo apt-get install -y nodejs npm install --global yarn diff --git a/doc/user/project/integrations/hangouts_chat.md b/doc/user/project/integrations/hangouts_chat.md index 3470d11b983..9f47b531f36 100644 --- a/doc/user/project/integrations/hangouts_chat.md +++ b/doc/user/project/integrations/hangouts_chat.md @@ -12,11 +12,11 @@ Hangouts). ## Integration workflow -To enable this integration, first you need to create a webhook for the room in +To enable this integration, you must first create a webhook for the space in Google Chat where you want to receive the notifications from your project. After that, enable the integration in GitLab and choose the events you want to -be notified about in your Google Chat room. +be notified about in your Google Chat space. For every selected event in your project, GitLab acts like a bot sending notifications to Google Chat: @@ -27,9 +27,9 @@ notifications to Google Chat: To enable the integration in Google Chat: -1. Enter the room where you want to receive notifications from GitLab. -1. In the upper-left corner, from the room dropdown list, select **Manage webhooks**. -1. Enter the name for your webhook, for example "GitLab integration". +1. Enter the space where you want to receive notifications from GitLab. +1. In the upper-left corner, from the space dropdown list, select **Apps and Integrations > Manage webhooks**. +1. Enter the name for your webhook (for example, `GitLab integration`). 1. Optional. Add an avatar for your bot. 1. Select **Save**. 1. Copy the webhook URL. @@ -56,9 +56,9 @@ To enable the integration in GitLab: 1. In your project, go to **Settings > Integrations** and select **Google Chat**. 1. Scroll down to the end of the page where you find a **Webhook** field. 1. Enter the webhook URL you copied from Google Chat. -1. Select the events you want to be notified about in your Google Chat room. +1. Select the events you want to be notified about in your Google Chat space. 1. Optional. Select **Test settings** to verify the connection. 1. Select **Save changes**. To test the integration, make a change based on the events you selected and -see the notification in your Google Chat room. +see the notification in your Google Chat space. diff --git a/lib/api/project_job_token_scope.rb b/lib/api/project_job_token_scope.rb index 5073d20be56..e9e25efea59 100644 --- a/lib/api/project_job_token_scope.rb +++ b/lib/api/project_job_token_scope.rb @@ -2,6 +2,8 @@ module API class ProjectJobTokenScope < ::API::Base + include PaginationParams + before { authenticate! } feature_category :secrets_management @@ -51,6 +53,26 @@ module API no_content! end + + desc 'Fetch project inbound allowlist for CI_JOB_TOKEN access settings.' do + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not found' } + ] + success status: 200, model: Entities::BasicProjectDetails + tags %w[projects_job_token_scope] + end + params do + use :pagination + end + get ':id/job_token_scope/allowlist' do + authorize_admin_project + + inbound_projects = ::Ci::JobToken::Scope.new(user_project).inbound_projects + + present paginate(inbound_projects), with: Entities::BasicProjectDetails + end end end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index d6863e4eba4..9c6d2d3d232 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -978,7 +978,7 @@ module API args[:permission_scope] = :transfer_projects groups = ::Groups::UserGroupsFinder.new(current_user, current_user, args).execute - groups = groups.with_route + groups = groups.excluding_groups(user_project.group).with_route present_groups(groups) end diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake index 2522488f579..b8a6e701876 100644 --- a/lib/tasks/gitlab/assets.rake +++ b/lib/tasks/gitlab/assets.rake @@ -15,6 +15,7 @@ module Tasks yarn.lock babel.config.js config/webpack.config.js + .nvmrc ].freeze # Ruby gems might emit assets which have an impact on compilation # or have a direct impact on asset compilation (e.g. scss) and therefore diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f3bd7c3eef6..1f3e9fc684e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -989,7 +989,7 @@ msgstr "" msgid "%{project_name}" msgstr "" -msgid "%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more.%{help_link_end}" +msgid "%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more%{help_link_end}." msgstr "" msgid "%{ref} cannot be added: %{error}" diff --git a/spec/features/oauth_registration_spec.rb b/spec/features/registrations/oauth_registration_spec.rb index c88a018a592..c88a018a592 100644 --- a/spec/features/oauth_registration_spec.rb +++ b/spec/features/registrations/oauth_registration_spec.rb diff --git a/spec/features/registrations/registration_spec.rb b/spec/features/registrations/registration_spec.rb new file mode 100644 index 00000000000..7a409b3934e --- /dev/null +++ b/spec/features/registrations/registration_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Registrations', feature_category: :system_access do + context 'when the user visits the registration page when already signed in', :clean_gitlab_redis_sessions do + let_it_be(:current_user) { create(:user) } + + before do + sign_in(current_user) + end + + it 'does not show an "You are already signed in" error message' do + visit new_user_registration_path + + wait_for_requests + + expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated')) + end + end +end diff --git a/spec/frontend/ci/runner/components/runner_jobs_empty_state_spec.js b/spec/frontend/ci/runner/components/runner_jobs_empty_state_spec.js index 59c9383cb31..b2dfc77bd99 100644 --- a/spec/frontend/ci/runner/components/runner_jobs_empty_state_spec.js +++ b/spec/frontend/ci/runner/components/runner_jobs_empty_state_spec.js @@ -1,4 +1,4 @@ -import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/pipelines_empty.svg?url'; +import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-pipeline-md.svg?url'; import { shallowMount } from '@vue/test-utils'; import { GlEmptyState } from '@gitlab/ui'; diff --git a/spec/frontend/ci/runner/components/runner_list_empty_state_spec.js b/spec/frontend/ci/runner/components/runner_list_empty_state_spec.js index 0de2759ea8a..9d521b0b8ca 100644 --- a/spec/frontend/ci/runner/components/runner_list_empty_state_spec.js +++ b/spec/frontend/ci/runner/components/runner_list_empty_state_spec.js @@ -1,5 +1,5 @@ -import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/pipelines_empty.svg?url'; -import FILTERED_SVG_URL from '@gitlab/svgs/dist/illustrations/magnifying-glass.svg?url'; +import EMPTY_STATE_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-pipeline-md.svg?url'; +import FILTERED_SVG_URL from '@gitlab/svgs/dist/illustrations/empty-state/empty-search-md.svg?url'; import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; import { s__ } from '~/locale'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js index f6021041b38..5b77d44c5bd 100644 --- a/spec/frontend/pipelines/pipelines_spec.js +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -60,7 +60,7 @@ describe('Pipelines', () => { const paths = { emptyStateSvgPath: '/assets/illustrations/empty-state/empty-pipeline-md.svg', errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg', - noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg', + noPipelinesSvgPath: '/assets/illustrations/empty-state/empty-pipeline-md.svg', ciLintPath: '/ci/lint', resetCachePath: `${mockProjectPath}/settings/ci_cd/reset_cache`, newPipelinePath: `${mockProjectPath}/pipelines/new`, @@ -71,7 +71,7 @@ describe('Pipelines', () => { const noPermissions = { emptyStateSvgPath: '/assets/illustrations/empty-state/empty-pipeline-md.svg', errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg', - noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg', + noPipelinesSvgPath: '/assets/illustrations/empty-state/empty-pipeline-md.svg', }; const defaultProps = { diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js index 7e14d292946..21b37f5cfbc 100644 --- a/spec/frontend/repository/components/blob_content_viewer_spec.js +++ b/spec/frontend/repository/components/blob_content_viewer_spec.js @@ -38,6 +38,7 @@ import { userPermissionsMock, propsMock, refMock, + axiosMockResponse, } from '../mock_data'; jest.mock('~/repository/components/blob_viewers'); @@ -61,6 +62,8 @@ const mockRouter = { push: mockRouterPush, }; +const legacyViewerUrl = 'some_file.js?format=json&viewer=simple'; + const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute = {}) => { Vue.use(VueApollo); @@ -79,8 +82,12 @@ const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute const blobInfo = { ...projectMock, repository: { + __typename: 'Repository', empty, - blobs: { nodes: [blob] }, + blobs: { + __typename: 'RepositoryBlobConnection', + nodes: [blob], + }, }, }; @@ -148,10 +155,6 @@ const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute }), ); - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ project: blobInfo, isBinary }); - await waitForPromises(); }; @@ -216,7 +219,6 @@ describe('Blob content viewer component', () => { }); describe('legacy viewers', () => { - const legacyViewerUrl = 'some_file.js?format=json&viewer=simple'; const fileType = 'text'; const highlightJs = false; @@ -437,8 +439,8 @@ describe('Blob content viewer component', () => { }); it('renders WebIdeLink button for binary files', async () => { - await createComponent({ blob: richViewerMock, isBinary: true }, mount); - + mockAxios.onGet(legacyViewerUrl).replyOnce(HTTP_STATUS_OK, axiosMockResponse); + await createComponent({}, mount); expect(findWebIdeLink().props()).toMatchObject({ editUrl: editBlobPath, webIdeUrl: ideEditPath, @@ -448,7 +450,8 @@ describe('Blob content viewer component', () => { describe('blob header binary file', () => { it('passes the correct isBinary value when viewing a binary file', async () => { - await createComponent({ blob: richViewerMock, isBinary: true }); + mockAxios.onGet(legacyViewerUrl).replyOnce(HTTP_STATUS_OK, axiosMockResponse); + await createComponent(); expect(findBlobHeader().props('isBinary')).toBe(true); }); diff --git a/spec/frontend/repository/mock_data.js b/spec/frontend/repository/mock_data.js index 399341d23a0..e20849d1085 100644 --- a/spec/frontend/repository/mock_data.js +++ b/spec/frontend/repository/mock_data.js @@ -198,3 +198,5 @@ export const paginatedTreeResponseFactory = ({ }, }, }); + +export const axiosMockResponse = { html: 'text', binary: true }; diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js index d87aa3194d2..9af44b1e212 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js @@ -71,8 +71,9 @@ const defaultScopedSlots = { 'suggestions-list': `<div data-testid="${mockSuggestionListTestId}" :data-suggestions="JSON.stringify(props.suggestions)"></div>`, }; +const mockConfig = { ...mockLabelToken, recentSuggestionsStorageKey: mockStorageKey }; const mockProps = { - config: { ...mockLabelToken, recentSuggestionsStorageKey: mockStorageKey }, + config: mockConfig, value: { data: '' }, active: false, suggestions: [], @@ -221,6 +222,20 @@ describe('BaseToken', () => { }); }, ); + + it('limits the length of the rendered list using config.maxSuggestions', () => { + mockSuggestions = ['a', 'b', 'c', 'd'].map((id) => ({ id })); + + const maxSuggestions = 2; + const config = { ...mockConfig, maxSuggestions }; + const props = { defaultSuggestions: [], suggestions: mockSuggestions, config }; + + getRecentlyUsedSuggestions.mockReturnValue([]); + wrapper = createComponent({ props, mountFn: shallowMountExtended, stubs: {} }); + + expect(findMockSuggestionList().exists()).toBe(true); + expect(getMockSuggestionListSuggestions().length).toEqual(maxSuggestions); + }); }); describe('with preloaded suggestions', () => { diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 67e4e128019..f7d0695b757 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -1040,6 +1040,28 @@ RSpec.describe Group, feature_category: :subgroups do end end + describe 'excluding_groups' do + let!(:another_group) { create(:group) } + + subject { described_class.excluding_groups(excluded_groups) } + + context 'when passing a single group' do + let(:excluded_groups) { group } + + it 'does not return excluded group' do + expect(subject).not_to include(group) + end + end + + context 'when passing an array with groups' do + let(:excluded_groups) { [group, another_group] } + + it 'does not return excluded groups' do + expect(subject).not_to include(group, another_group) + end + end + end + describe 'accessible_to_user' do subject { described_class.accessible_to_user(user) } diff --git a/spec/requests/api/project_job_token_scope_spec.rb b/spec/requests/api/project_job_token_scope_spec.rb index c070e4daf01..23c27c8ce13 100644 --- a/spec/requests/api/project_job_token_scope_spec.rb +++ b/spec/requests/api/project_job_token_scope_spec.rb @@ -193,4 +193,74 @@ RSpec.describe API::ProjectJobTokenScope, feature_category: :secrets_management end end end + + describe "GET /projects/:id/job_token_scope/allowlist" do + let_it_be(:project) { create(:project, :public) } + + let_it_be(:user) { create(:user) } + + let(:get_job_token_scope_allowlist_path) { "/projects/#{project.id}/job_token_scope/allowlist" } + + subject { get api(get_job_token_scope_allowlist_path, user) } + + context 'when unauthenticated user (missing user)' do + context 'for public project' do + it 'does not return ci cd settings of job token' do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + + get api(get_job_token_scope_allowlist_path) + + expect(response).to have_gitlab_http_status(:unauthorized) + end + end + end + + context 'when authenticated user as maintainer' do + before_all { project.add_maintainer(user) } + + it 'returns allowlist containing only the source projects' do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_present + expect(json_response).to include hash_including("id" => project.id) + end + + it 'returns allowlist of project' do + create(:ci_job_token_project_scope_link, source_project: project, direction: :inbound) + create(:ci_job_token_project_scope_link, source_project: project, direction: :outbound) + + ci_job_token_project_scope_link = + create( + :ci_job_token_project_scope_link, + source_project: project, + direction: :inbound + ) + + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq 3 + expect(json_response).to include( + hash_including("id" => project.id), + hash_including("id" => ci_job_token_project_scope_link.target_project.id) + ) + end + + context 'when authenticated user as developer' do + before do + project.add_developer(user) + end + + it 'returns forbidden and no ci cd settings for public project' do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + + subject + + expect(response).to have_gitlab_http_status(:forbidden) + end + end + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 349101a092f..5dde98ceeef 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -5154,7 +5154,7 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :projects d it 'includes groups where the user has permissions to transfer a project to' do request - expect(project_ids_from_response).to include(maintainer_group.id, owner_group.id) + expect(project_ids_from_response).to match_array [maintainer_group.id, owner_group.id] end it 'does not include groups where the user doesn not have permissions to transfer a project' do @@ -5163,6 +5163,12 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :projects d expect(project_ids_from_response).not_to include(guest_group.id) end + it 'does not include the group id of the current project' do + request + + expect(project_ids_from_response).not_to include(project.group.id) + end + context 'with search' do let(:params) { { search: 'maintainer' } } diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 0accb341cb9..5f60f8a6bfa 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -86,8 +86,7 @@ module CycleAnalyticsHelpers def select_value_stream(value_stream_name) toggle_value_stream_dropdown - - page.find('[data-testid="dropdown-value-streams"]').all('li button').find { |item| item.text == value_stream_name.to_s }.click + page.find('[data-testid="dropdown-value-streams"]').all('li span').find { |item| item.text == value_stream_name.to_s }.click wait_for_requests end diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb index 526a56e7dab..a484452b9e9 100644 --- a/spec/support/shared_examples/features/work_items_shared_examples.rb +++ b/spec/support/shared_examples/features/work_items_shared_examples.rb @@ -67,7 +67,7 @@ RSpec.shared_examples 'work items comments' do |type| find('[data-testid="work-item-note-actions"]', match: :first).click expect(page).to have_selector('[data-testid="copy-link-action"]') - expect(page).not_to have_selector('[data-testid="assign-note-action"]') + expect(page).to have_selector('[data-testid="assign-note-action"]') end end end diff --git a/spec/tooling/docs/deprecation_handling_spec.rb b/spec/tooling/docs/deprecation_handling_spec.rb index 78e613c37c7..feedd246e94 100644 --- a/spec/tooling/docs/deprecation_handling_spec.rb +++ b/spec/tooling/docs/deprecation_handling_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Docs::DeprecationHandling do ['14-10-c.yml', '14-2-b.yml', '14-2-a.yml'] ) # Create dummy YAML data based on file name - allow(YAML).to receive(:load_file) do |file_name| + allow(YAML).to receive(:safe_load_file) do |file_name| { 'title' => file_name[/[a-z]*\.yml/], 'removal_milestone' => file_name[/\d+-\d+/].tr('-', '.') diff --git a/tooling/docs/deprecation_handling.rb b/tooling/docs/deprecation_handling.rb index 5996a0c89c1..320f5df0b07 100644 --- a/tooling/docs/deprecation_handling.rb +++ b/tooling/docs/deprecation_handling.rb @@ -17,7 +17,7 @@ module Docs end entries = source_file_paths.flat_map do |file| - YAML.load_file(file) + YAML.safe_load_file(file, permitted_classes: [Date]) end entries = entries.sort_by { |d| d["title"] } |