Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-11 03:09:21 +00:00
parent 4f1e40017d
commit 8754d20bbb
45 changed files with 553 additions and 84 deletions

View File

@ -99,10 +99,10 @@ export default {
</gl-sprintf>
</div>
<div class="gl-ml-auto">
<gl-button data-testid="clear-btn" variant="default" @click="onClearChecked">{{
<gl-button variant="default" @click="onClearChecked">{{
s__('Runners|Clear selection')
}}</gl-button>
<gl-button data-testid="delete-btn" variant="danger" @click="onClickDelete">{{
<gl-button variant="danger" @click="onClickDelete">{{
s__('Runners|Delete selected')
}}</gl-button>
</div>

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Users
class ProjectCalloutsController < Users::CalloutsController
private
def callout
Users::DismissProjectCalloutService.new(
container: nil, current_user: current_user, params: callout_params
).execute
end
def callout_params
params.permit(:project_id).merge(feature_name: feature_name)
end
end
end

View File

@ -291,6 +291,8 @@ class Project < ApplicationRecord
has_many :project_members, -> { where(requested_at: nil) },
as: :source, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :project_callouts, class_name: 'Users::ProjectCallout', foreign_key: :project_id
alias_method :members, :project_members
has_many :users, through: :project_members

View File

@ -222,6 +222,7 @@ class User < ApplicationRecord
has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'Users::Callout'
has_many :group_callouts, class_name: 'Users::GroupCallout'
has_many :project_callouts, class_name: 'Users::ProjectCallout'
has_many :namespace_callouts, class_name: 'Users::NamespaceCallout'
has_many :term_agreements
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
@ -2087,6 +2088,12 @@ class User < ApplicationRecord
callout_dismissed?(callout, ignore_dismissal_earlier_than)
end
def dismissed_callout_for_project?(feature_name:, project:, ignore_dismissal_earlier_than: nil)
callout = project_callouts.find_by(feature_name: feature_name, project: project)
callout_dismissed?(callout, ignore_dismissal_earlier_than)
end
# Load the current highest access by looking directly at the user's memberships
def current_highest_access_level
members.non_request.maximum(:access_level)
@ -2118,6 +2125,11 @@ class User < ApplicationRecord
.find_or_initialize_by(feature_name: ::Users::NamespaceCallout.feature_names[feature_name], namespace_id: namespace_id)
end
def find_or_initialize_project_callout(feature_name, project_id)
project_callouts
.find_or_initialize_by(feature_name: ::Users::ProjectCallout.feature_names[feature_name], project_id: project_id)
end
def can_trigger_notifications?
confirmed? && !blocked? && !ghost?
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Users
class ProjectCallout < ApplicationRecord
include Users::Calloutable
self.table_name = 'user_project_callouts'
belongs_to :project
enum feature_name: {
awaiting_members_banner: 1 # EE-only
}
validates :project, presence: true
validates :feature_name,
presence: true,
uniqueness: { scope: [:user_id, :project_id] },
inclusion: { in: ProjectCallout.feature_names.keys }
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module Users
class DismissProjectCalloutService < DismissCalloutService
private
def callout
current_user.find_or_initialize_project_callout(params[:feature_name], params[:project_id])
end
end
end

View File

@ -65,6 +65,7 @@ scope '-/users', module: :users do
resources :callouts, only: [:create]
resources :group_callouts, only: [:create]
resources :project_callouts, only: [:create]
end
scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) do

View File

@ -0,0 +1,9 @@
---
table_name: user_project_callouts
classes:
- Users::ProjectCallout
feature_categories:
- navigation
description: Adds the ability to track a user callout being dismissed by project
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94144
milestone: '15.3'

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class CreateUserProjectCallout < Gitlab::Database::Migration[2.0]
def up
create_table :user_project_callouts do |t|
t.bigint :user_id, null: false
t.bigint :project_id, null: false
t.integer :feature_name, limit: 2, null: false
t.datetime_with_timezone :dismissed_at
t.index :project_id
t.index [:user_id, :feature_name, :project_id], unique: true, name: 'index_project_user_callouts_feature'
end
end
def down
drop_table :user_project_callouts
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddProjectIdFkeyForUserProjectCallout < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :user_project_callouts, :projects, column: :project_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :user_project_callouts, column: :project_id
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddUserIdFkeyForUserProjectCallout < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
add_concurrent_foreign_key :user_project_callouts, :users, column: :user_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :user_project_callouts, column: :user_id
end
end
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddTimestampsToProjectStatistics < Gitlab::Database::Migration[2.0]
def change
add_timestamps_with_timezone(:project_statistics, null: false, default: -> { 'NOW()' })
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class UpdateIndexVulnerabilitiesProjectIdId < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
NEW_INDEX_NAME = 'index_vulnerabilities_project_id_and_id_on_default_branch'
OLD_INDEX_NAME = 'index_vulnerabilities_on_project_id_and_id'
def up
add_concurrent_index :vulnerabilities, [:project_id, :id],
where: 'present_on_default_branch IS TRUE',
name: NEW_INDEX_NAME
remove_concurrent_index_by_name(:vulnerabilities, OLD_INDEX_NAME)
end
def down
add_concurrent_index :vulnerabilities, [:project_id, :id], name: OLD_INDEX_NAME
remove_concurrent_index_by_name(:vulnerabilities, NEW_INDEX_NAME)
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class RemoveCiNamespaceMonthlyUsagesAdditionalAmountAvailableColumn < Gitlab::Database::Migration[2.0]
def up
remove_column :ci_namespace_monthly_usages, :additional_amount_available
end
def down
add_column :ci_namespace_monthly_usages, :additional_amount_available, :integer, default: 0, null: false
end
end

View File

@ -0,0 +1 @@
f1d4faf4d32a3271a97b389d53c9d3accbfa3fa2bd47d63257fe589efa4bb665

View File

@ -0,0 +1 @@
bf12037cb99a399302610f948dad48589eca4e631d82d9f26b04bae882b10020

View File

@ -0,0 +1 @@
047147acc972ab8681f097d5060998a47e44612fde7f2137714683bd61350c2d

View File

@ -0,0 +1 @@
2cdf4c4fe218a5fb7061bf65643868c7b592cd3ef0d7611949e8fd86bc635c24

View File

@ -0,0 +1 @@
07488e8c6ea0f3dc92e1370efb0190facf520b850e170fcd8f3ce0e2a15c096a

View File

@ -0,0 +1 @@
bab4f4d3aaedd698400fcbd5991797530450fe845a8034b03b1bf525a61e628a

View File

@ -12856,7 +12856,6 @@ CREATE TABLE ci_namespace_monthly_usages (
id bigint NOT NULL,
namespace_id bigint NOT NULL,
date date NOT NULL,
additional_amount_available integer DEFAULT 0 NOT NULL,
amount_used numeric(18,2) DEFAULT 0.0 NOT NULL,
notification_level smallint DEFAULT 100 NOT NULL,
shared_runners_duration integer DEFAULT 0 NOT NULL,
@ -19839,7 +19838,9 @@ CREATE TABLE project_statistics (
snippets_size bigint,
pipeline_artifacts_size bigint DEFAULT 0 NOT NULL,
uploads_size bigint DEFAULT 0 NOT NULL,
container_registry_size bigint DEFAULT 0 NOT NULL
container_registry_size bigint DEFAULT 0 NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
updated_at timestamp with time zone DEFAULT now() NOT NULL
);
CREATE SEQUENCE project_statistics_id_seq
@ -21906,6 +21907,23 @@ CREATE SEQUENCE user_preferences_id_seq
ALTER SEQUENCE user_preferences_id_seq OWNED BY user_preferences.id;
CREATE TABLE user_project_callouts (
id bigint NOT NULL,
user_id bigint NOT NULL,
project_id bigint NOT NULL,
feature_name smallint NOT NULL,
dismissed_at timestamp with time zone
);
CREATE SEQUENCE user_project_callouts_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE user_project_callouts_id_seq OWNED BY user_project_callouts.id;
CREATE TABLE user_statuses (
user_id integer NOT NULL,
cached_markdown_version integer,
@ -23788,6 +23806,8 @@ ALTER TABLE ONLY user_permission_export_uploads ALTER COLUMN id SET DEFAULT next
ALTER TABLE ONLY user_preferences ALTER COLUMN id SET DEFAULT nextval('user_preferences_id_seq'::regclass);
ALTER TABLE ONLY user_project_callouts ALTER COLUMN id SET DEFAULT nextval('user_project_callouts_id_seq'::regclass);
ALTER TABLE ONLY user_statuses ALTER COLUMN user_id SET DEFAULT nextval('user_statuses_user_id_seq'::regclass);
ALTER TABLE ONLY user_synced_attributes_metadata ALTER COLUMN id SET DEFAULT nextval('user_synced_attributes_metadata_id_seq'::regclass);
@ -26076,6 +26096,9 @@ ALTER TABLE ONLY user_permission_export_uploads
ALTER TABLE ONLY user_preferences
ADD CONSTRAINT user_preferences_pkey PRIMARY KEY (id);
ALTER TABLE ONLY user_project_callouts
ADD CONSTRAINT user_project_callouts_pkey PRIMARY KEY (id);
ALTER TABLE ONLY user_statuses
ADD CONSTRAINT user_statuses_pkey PRIMARY KEY (user_id);
@ -29484,6 +29507,8 @@ CREATE UNIQUE INDEX index_project_topics_on_project_id_and_topic_id ON project_t
CREATE INDEX index_project_topics_on_topic_id ON project_topics USING btree (topic_id);
CREATE UNIQUE INDEX index_project_user_callouts_feature ON user_project_callouts USING btree (user_id, feature_name, project_id);
CREATE INDEX index_projects_aimed_for_deletion ON projects USING btree (marked_for_deletion_at) WHERE ((marked_for_deletion_at IS NOT NULL) AND (pending_delete = false));
CREATE INDEX index_projects_api_created_at_id_desc ON projects USING btree (created_at, id DESC);
@ -30122,6 +30147,8 @@ CREATE INDEX index_user_preferences_on_gitpod_enabled ON user_preferences USING
CREATE UNIQUE INDEX index_user_preferences_on_user_id ON user_preferences USING btree (user_id);
CREATE INDEX index_user_project_callouts_on_project_id ON user_project_callouts USING btree (project_id);
CREATE INDEX index_user_statuses_on_clear_status_at_not_null ON user_statuses USING btree (clear_status_at) WHERE (clear_status_at IS NOT NULL);
CREATE INDEX index_user_statuses_on_user_id ON user_statuses USING btree (user_id);
@ -30218,8 +30245,6 @@ CREATE INDEX index_vulnerabilities_on_last_edited_by_id ON vulnerabilities USING
CREATE INDEX index_vulnerabilities_on_milestone_id ON vulnerabilities USING btree (milestone_id);
CREATE INDEX index_vulnerabilities_on_project_id_and_id ON vulnerabilities USING btree (project_id, id);
CREATE INDEX index_vulnerabilities_on_project_id_and_id_active_cis ON vulnerabilities USING btree (project_id, id) WHERE ((report_type = 7) AND (state = ANY (ARRAY[1, 4])));
CREATE INDEX index_vulnerabilities_on_project_id_and_state_and_severity ON vulnerabilities USING btree (project_id, state, severity);
@ -30234,6 +30259,8 @@ CREATE INDEX index_vulnerabilities_on_state_case_id_desc ON vulnerabilities USIN
CREATE INDEX index_vulnerabilities_on_updated_by_id ON vulnerabilities USING btree (updated_by_id);
CREATE INDEX index_vulnerabilities_project_id_and_id_on_default_branch ON vulnerabilities USING btree (project_id, id) WHERE (present_on_default_branch IS TRUE);
CREATE INDEX index_vulnerabilities_project_id_state_severity_default_branch ON vulnerabilities USING btree (project_id, state, severity, present_on_default_branch);
CREATE INDEX index_vulnerability_exports_on_author_id ON vulnerability_exports USING btree (author_id);
@ -32043,6 +32070,9 @@ ALTER TABLE ONLY namespaces
ALTER TABLE ONLY issue_tracker_data
ADD CONSTRAINT fk_33921c0ee1 FOREIGN KEY (integration_id) REFERENCES integrations(id) ON DELETE CASCADE;
ALTER TABLE ONLY user_project_callouts
ADD CONSTRAINT fk_33b4814f6b FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY namespaces
ADD CONSTRAINT fk_3448c97865 FOREIGN KEY (push_rule_id) REFERENCES push_rules(id) ON DELETE SET NULL;
@ -32739,6 +32769,9 @@ ALTER TABLE ONLY analytics_devops_adoption_segments
ALTER TABLE ONLY boards_epic_list_user_preferences
ADD CONSTRAINT fk_f5f2fe5c1f FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY user_project_callouts
ADD CONSTRAINT fk_f62dd11a33 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY cluster_agents
ADD CONSTRAINT fk_f7d43dee13 FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL;

View File

@ -7,8 +7,18 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Repository checks **(FREE SELF)**
You can use [`git fsck`](https://git-scm.com/docs/git-fsck) to verify the integrity of all data
committed to a repository. GitLab administrators can trigger this check for a project using the
GitLab UI:
committed to a repository. GitLab administrators can:
- Manually trigger this check for a project, using the GitLab UI.
- Schedule this check to run automatically for all projects.
- Run this check from the command line.
- Run a [Rake task](raketasks/check.md#repository-integrity) for checking Git repositories, which can be used to run
`git fsck` against all repositories and generate repository checksums, as a way to compare repositories on different
servers.
## Check a project's repository using GitLab UI
To check a project's repository using GitLab UI:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Overview > Projects**.
@ -18,9 +28,7 @@ GitLab UI:
The checks run asynchronously so it may take a few minutes before the check result is visible on the
project page in the Admin Area. If the checks fail, see [what to do](#what-to-do-if-a-check-failed).
This setting is off by default, because it can cause many false alarms.
## Enable periodic checks
## Enable repository checks for all projects
Instead of checking repositories manually, GitLab can be configured to run the checks periodically:
@ -45,10 +53,27 @@ the start of Sunday.
Repositories with known check failures can be found at
`/admin/projects?last_repository_check_failed=1`.
## Run a check using the command line
You can run [`git fsck`](https://git-scm.com/docs/git-fsck) using the command line on repositories on
[Gitaly servers](gitaly/index.md). To locate the repositories:
1. Go to the storage location for repositories:
- For Omnibus GitLab installations, repositories are stored in the `/var/opt/gitlab/git-data/repositories` directory
by default.
- For GitLab Helm chart installations, repositories are stored in the `/home/git/repositories` directory inside the
Gitaly pod by default.
1. [Identify the subdirectory that contains the repository](repository_storage_types.md#from-project-name-to-hashed-path)
that you need to check.
1. Run the check. For example:
```shell
sudo /opt/gitlab/embedded/bin/git -C /var/opt/gitlab/git-data/repositories/@hashed/0b/91/0b91...f9.git fsck
```
## What to do if a check failed
If a repository check fails, locate the error in the [`repocheck.log` file](logs/index.md#repochecklog) on
disk at:
If a repository check fails, locate the error in the [`repocheck.log` file](logs/index.md#repochecklog) on disk at:
- `/var/log/gitlab/gitlab-rails` for Omnibus GitLab installations.
- `/home/git/gitlab/log` for installations from source.
@ -60,24 +85,3 @@ If periodic repository checks cause false alarms, you can clear all repository c
1. On the left sidebar, select **Settings > Repository** (`/admin/application_settings/repository`).
1. Expand the **Repository maintenance** section.
1. Select **Clear all repository checks**.
## Run a check using the command line
You can run [`git fsck`](https://git-scm.com/docs/git-fsck) using the command line on repositories
on [Gitaly servers](gitaly/index.md). To locate the repositories:
1. Go to the storage location for repositories:
- For Omnibus GitLab installations, repositories are stored in the `/var/opt/gitlab/git-data/repositories` directory by default.
- For GitLab Helm chart installations, repositories are stored in the `/home/git/repositories` directory inside the Gitaly pod by default.
1. [Identify the subdirectory that contains the repository](repository_storage_types.md#from-project-name-to-hashed-path)
that you need to check.
To run a check (for example):
```shell
sudo /opt/gitlab/embedded/bin/git -C /var/opt/gitlab/git-data/repositories/@hashed/0b/91/0b91...f9.git fsck
```
You can also run [Rake tasks](raketasks/check.md#repository-integrity) for checking Git
repositories, which can be used to run `git fsck` against all repositories and generate repository
checksums, as a way to compare repositories on different servers.

View File

@ -109,6 +109,8 @@ maximum of two directory levels from the repository's root. For example, the
`gemnasium-dependency_scanning` job is enabled if a repository contains either `Gemfile`,
`api/Gemfile`, or `api/client/Gemfile`, but not if the only supported dependency file is `api/v1/client/Gemfile`.
For Java and Python, when a supported depedency file is detected, Dependency Scanning attempts to build the project and execute some Java or Python commands to get the list of dependencies. For all other projects, the lock file is parsed to obtain the list of dependencies without needing to build the project first.
When a supported dependency file is detected, all dependencies, including transitive dependencies are analyzed. There is no limit to the depth of nested or transitive dependencies that are analyzed.
The following languages and dependency managers are supported:
@ -148,14 +150,13 @@ table.supported-languages ul {
<th>Language Versions</th>
<th>Package Manager</th>
<th>Supported files</th>
<th>Analyzer</th>
<th><a href="#how-multiple-files-are-processed">Processes multiple files?</a></th>
</tr>
</thead>
<tbody>
<tr>
<td>Ruby</td>
<td>Not applicable</td>
<td>All versions</td>
<td><a href="https://bundler.io/">Bundler</a></td>
<td>
<ul>
@ -163,23 +164,20 @@ table.supported-languages ul {
<li><code>gems.locked</code></li>
</ul>
</td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>Y</td>
</tr>
<tr>
<td>PHP</td>
<td>Not applicable</td>
<td>All versions</td>
<td><a href="https://getcomposer.org/">Composer</a></td>
<td><code>composer.lock</code></td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>Y</td>
</tr>
<tr>
<td>C</td>
<td rowspan="2">Not applicable</td>
<td rowspan="2">All versions</td>
<td rowspan="2"><a href="https://conan.io/">Conan</a></td>
<td rowspan="2"><a href="https://docs.conan.io/en/latest/versioning/lockfiles.html"><code>conan.lock</code></a></td>
<td rowspan="2"><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td rowspan="2">Y</td>
</tr>
<tr>
@ -187,10 +185,9 @@ table.supported-languages ul {
</tr>
<tr>
<td>Go</td>
<td>Not applicable</td>
<td>All versions</td>
<td><a href="https://go.dev/">Go</a></td>
<td><code>go.sum</code></td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>Y</td>
</tr>
<tr>
@ -211,41 +208,36 @@ table.supported-languages ul {
<li><code>build.gradle.kts</code></li>
</ul>
</td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>N</td>
</tr>
<tr>
<td><a href="https://maven.apache.org/">Maven</a></td>
<td><code>pom.xml</code></td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>N</td>
</tr>
<tr>
<td rowspan="2">JavaScript</td>
<td>Not applicable</td>
<td>All versions</td>
<td><a href="https://www.npmjs.com/">npm</a></td>
<td>
<ul>
<li><code>package-lock.json</code></li>
<li><code>package-lock.json</code><sup><b><a href="#notes-regarding-supported-languages-and-package-managers-3">3</a></b></sup></li>
<li><code>npm-shrinkwrap.json</code></li>
</ul>
</td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>Y</td>
</tr>
<tr>
<td>Not applicable</td>
<td>All versions</td>
<td><a href="https://classic.yarnpkg.com/en/">yarn</a></td>
<td><code>yarn.lock</code></td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>Y</td>
</tr>
<tr>
<td>.NET</td>
<td rowspan="2">Not applicable</td>
<td rowspan="2">All versions</td>
<td rowspan="2"><a href="https://www.nuget.org/">NuGet</a></td>
<td rowspan="2"><a href="https://docs.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#enabling-lock-file"><code>packages.lock.json</code></a></td>
<td rowspan="2"><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td rowspan="2">Y</td>
</tr>
<tr>
@ -256,7 +248,6 @@ table.supported-languages ul {
<td rowspan="4">3.9</td>
<td><a href="https://setuptools.readthedocs.io/en/latest/">setuptools</a></td>
<td><code>setup.py</code></td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>N</td>
</tr>
<tr>
@ -268,7 +259,6 @@ table.supported-languages ul {
<li><code>requires.txt</code></li>
</ul>
</td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>N</td>
</tr>
<tr>
@ -276,24 +266,21 @@ table.supported-languages ul {
<td>
<ul>
<li><a href="https://pipenv.pypa.io/en/latest/basics/#example-pipfile-pipfile-lock"><code>Pipfile</code></a></li>
<li><a href="https://pipenv.pypa.io/en/latest/basics/#example-pipfile-pipfile-lock"><code>Pipfile.lock</code></a><sup><b><a href="#notes-regarding-supported-languages-and-package-managers-3">3</a></b></sup></li>
<li><a href="https://pipenv.pypa.io/en/latest/basics/#example-pipfile-pipfile-lock"><code>Pipfile.lock</code></a><sup><b><a href="#notes-regarding-supported-languages-and-package-managers-4">4</a></b></sup></li>
</ul>
</td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>N</td>
</tr>
<tr>
<td><a href="https://python-poetry.org/">Poetry</a><sup><b><a href="#notes-regarding-supported-languages-and-package-managers-5">5</a></b></sup></td>
<td><code>poetry.lock</code></td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>N</td>
</tr>
<tr>
<td>Scala</td>
<td>Not applicable</td>
<td><a href="https://www.scala-sbt.org/">sbt</a><sup><b><a href="#notes-regarding-supported-languages-and-package-managers-4">4</a></b></sup></td>
<td>All versions</td>
<td><a href="https://www.scala-sbt.org/">sbt</a><sup><b><a href="#notes-regarding-supported-languages-and-package-managers-6">6</a></b></sup></td>
<td><code>build.sbt</code></td>
<td><a href="https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium">Gemnasium</a></td>
<td>N</td>
</tr>
</tbody>
@ -316,6 +303,12 @@ table.supported-languages ul {
</li>
<li>
<a id="notes-regarding-supported-languages-and-package-managers-3"></a>
<p>
npm is only supported when `lockfileVersion = 1` or `lockfileVersion = 2`. Work to add support for `lockfileVersion = 3` is being tracked in issue <a href="https://gitlab.com/gitlab-org/gitlab/-/issues/365176">GitLab#365176</a>.
</p>
</li>
<li>
<a id="notes-regarding-supported-languages-and-package-managers-4"></a>
<p>
The presence of a <code>Pipfile.lock</code> file alone will <i>not</i> trigger the analyzer; the presence of a <code>Pipfile</code> is
still required in order for the analyzer to be executed. However, if a <code>Pipfile.lock</code> file is found, it will be used by
@ -327,12 +320,6 @@ table.supported-languages ul {
installing project dependencies</a>.
</p>
</li>
<li>
<a id="notes-regarding-supported-languages-and-package-managers-4"></a>
<p>
Support for <a href="https://www.scala-sbt.org/">sbt</a> 1.3 and above was added in GitLab 13.9.
</p>
</li>
<li>
<a id="notes-regarding-supported-languages-and-package-managers-5"></a>
<p>
@ -341,6 +328,12 @@ table.supported-languages ul {
<a href="https://gitlab.com/gitlab-org/gitlab/-/issues/32774">Poetry's pyproject.toml support for dependency scanning.</a>
</p>
</li>
<li>
<a id="notes-regarding-supported-languages-and-package-managers-6"></a>
<p>
Support for <a href="https://www.scala-sbt.org/">sbt</a> 1.3 and above was added in GitLab 13.9.
</p>
</li>
</ol>
<!-- markdownlint-enable MD044 -->

View File

@ -68,14 +68,22 @@ fingerprint.
## Okta
Basic SAML app configuration:
Basic SAML app configuration for GitLab.com groups:
![Okta basic SAML](img/Okta-SAMLsetup.png)
![Okta basic SAML](img/Okta-GroupSAML.png)
Basic SAML app configuration for GitLab self-managed:
![Okta admin panel view](img/Okta-SM.png)
User claims and attributes:
![Okta Attributes](img/Okta-attributes.png)
Groups attribute:
![Okta Group attribute](img/Okta-GroupAttribute.png)
Advanced SAML app settings (defaults):
![Okta Advanced Settings](img/Okta-advancedsettings.png)
@ -88,10 +96,6 @@ Sign on settings:
![Okta SAML settings](img/okta_saml_settings.png)
Self-managed instance example:
![Okta admin panel view](img/okta_admin_panel_v13_9.png)
Setting the username for the newly provisioned users when assigning them the SCIM app:
![Assigning SCIM app to users on Okta](img/okta_setting_username.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -531,6 +531,7 @@ user_custom_attributes: :gitlab_main
user_details: :gitlab_main
user_follow_users: :gitlab_main
user_group_callouts: :gitlab_main
user_project_callouts: :gitlab_main
user_highest_roles: :gitlab_main
user_interacted_projects: :gitlab_main
user_permission_export_uploads: :gitlab_main

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
FactoryBot.define do
factory :project_callout, class: 'Users::ProjectCallout' do
feature_name { :awaiting_members_banner }
user
project
end
end

View File

@ -6,6 +6,20 @@ const vNodeContainsText = (vnode, text) =>
(vnode.text && vnode.text.includes(text)) ||
(vnode.children && vnode.children.filter((child) => vNodeContainsText(child, text)).length);
/**
* Create a VTU wrapper from an element.
*
* If a Vue instance manages the element, the wrapper is created
* with that Vue instance.
*
* @param {HTMLElement} element
* @param {Object} options
* @returns VTU wrapper
*/
const createWrapperFromElement = (element, options) =>
// eslint-disable-next-line no-underscore-dangle
createWrapper(element.__vue__ || element, options || {});
/**
* Determines whether a `shallowMount` Wrapper contains text
* within one of it's slots. This will also work on Wrappers
@ -85,8 +99,7 @@ export const extendedWrapper = (wrapper) => {
if (!elements.length) {
return new ErrorWrapper(query);
}
return createWrapper(elements[0], this.options || {});
return createWrapperFromElement(elements[0], this.options);
},
},
};
@ -104,7 +117,7 @@ export const extendedWrapper = (wrapper) => {
);
const wrappers = elements.map((element) => {
const elementWrapper = createWrapper(element, this.options || {});
const elementWrapper = createWrapperFromElement(element, this.options);
elementWrapper.selector = text;
return elementWrapper;

View File

@ -6,6 +6,7 @@ import {
WrapperArray as VTUWrapperArray,
ErrorWrapper as VTUErrorWrapper,
} from '@vue/test-utils';
import Vue from 'vue';
import {
extendedWrapper,
shallowMountExtended,
@ -139,9 +140,12 @@ describe('Vue test utils helpers', () => {
const text = 'foo bar';
const options = { selector: 'div' };
const mockDiv = document.createElement('div');
const mockVm = new Vue({ render: (h) => h('div') }).$mount();
let wrapper;
beforeEach(() => {
jest.spyOn(vtu, 'createWrapper');
wrapper = extendedWrapper(
shallowMount({
template: `<div>foo bar</div>`,
@ -164,7 +168,6 @@ describe('Vue test utils helpers', () => {
describe('when element is found', () => {
beforeEach(() => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv]);
jest.spyOn(vtu, 'createWrapper');
});
it('returns a VTU wrapper', () => {
@ -172,14 +175,27 @@ describe('Vue test utils helpers', () => {
expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options);
expect(result).toBeInstanceOf(VTUWrapper);
expect(result.vm).toBeUndefined();
});
});
describe('when a Vue instance element is found', () => {
beforeEach(() => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockVm.$el]);
});
it('returns a VTU wrapper', () => {
const result = wrapper[findMethod](text, options);
expect(vtu.createWrapper).toHaveBeenCalledWith(mockVm, wrapper.options);
expect(result).toBeInstanceOf(VTUWrapper);
expect(result.vm).toBeInstanceOf(Vue);
});
});
describe('when multiple elements are found', () => {
beforeEach(() => {
const mockSpan = document.createElement('span');
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => [mockDiv, mockSpan]);
jest.spyOn(vtu, 'createWrapper');
});
it('returns the first element as a VTU wrapper', () => {
@ -187,6 +203,24 @@ describe('Vue test utils helpers', () => {
expect(vtu.createWrapper).toHaveBeenCalledWith(mockDiv, wrapper.options);
expect(result).toBeInstanceOf(VTUWrapper);
expect(result.vm).toBeUndefined();
});
});
describe('when multiple Vue instances are found', () => {
beforeEach(() => {
const mockVm2 = new Vue({ render: (h) => h('span') }).$mount();
jest
.spyOn(testingLibrary, expectedQuery)
.mockImplementation(() => [mockVm.$el, mockVm2.$el]);
});
it('returns the first element as a VTU wrapper', () => {
const result = wrapper[findMethod](text, options);
expect(vtu.createWrapper).toHaveBeenCalledWith(mockVm, wrapper.options);
expect(result).toBeInstanceOf(VTUWrapper);
expect(result.vm).toBeInstanceOf(Vue);
});
});
@ -211,12 +245,17 @@ describe('Vue test utils helpers', () => {
${'findAllByAltText'} | ${'queryAllByAltText'}
`('$findMethod', ({ findMethod, expectedQuery }) => {
const text = 'foo bar';
const options = { selector: 'div' };
const options = { selector: 'li' };
const mockElements = [
document.createElement('li'),
document.createElement('li'),
document.createElement('li'),
];
const mockVms = [
new Vue({ render: (h) => h('li') }).$mount(),
new Vue({ render: (h) => h('li') }).$mount(),
new Vue({ render: (h) => h('li') }).$mount(),
];
let wrapper;
beforeEach(() => {
@ -245,9 +284,13 @@ describe('Vue test utils helpers', () => {
);
});
describe('when elements are found', () => {
describe.each`
case | mockResult | isVueInstance
${'HTMLElements'} | ${mockElements} | ${false}
${'Vue instance elements'} | ${mockVms} | ${true}
`('when $case are found', ({ mockResult, isVueInstance }) => {
beforeEach(() => {
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockElements);
jest.spyOn(testingLibrary, expectedQuery).mockImplementation(() => mockResult);
});
it('returns a VTU wrapper array', () => {
@ -257,7 +300,9 @@ describe('Vue test utils helpers', () => {
expect(
result.wrappers.every(
(resultWrapper) =>
resultWrapper instanceof VTUWrapper && resultWrapper.options === wrapper.options,
resultWrapper instanceof VTUWrapper &&
resultWrapper.vm instanceof Vue === isVueInstance &&
resultWrapper.options === wrapper.options,
),
).toBe(true);
expect(result.length).toBe(3);

View File

@ -2,6 +2,7 @@ import Vue from 'vue';
import { GlSprintf } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { s__ } from '~/locale';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import RunnerBulkDelete from '~/runner/components/runner_bulk_delete.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
@ -17,8 +18,8 @@ describe('RunnerBulkDelete', () => {
let mockState;
let mockCheckedRunnerIds;
const findClearBtn = () => wrapper.findByTestId('clear-btn');
const findDeleteBtn = () => wrapper.findByTestId('delete-btn');
const findClearBtn = () => wrapper.findByText(s__('Runners|Clear selection'));
const findDeleteBtn = () => wrapper.findByText(s__('Runners|Delete selected'));
const createComponent = () => {
const { cacheConfig, localMutations } = mockState;

View File

@ -627,6 +627,7 @@ project:
- security_trainings
- vulnerability_reads
- build_artifacts_size_refresh
- project_callouts
award_emoji:
- awardable
- user

View File

@ -147,6 +147,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to have_many(:build_trace_chunks).through(:builds).dependent(:restrict_with_error) }
it { is_expected.to have_many(:secure_files).class_name('Ci::SecureFile').dependent(:restrict_with_error) }
it { is_expected.to have_one(:build_artifacts_size_refresh).class_name('Projects::BuildArtifactsSizeRefresh') }
it { is_expected.to have_many(:project_callouts).class_name('Users::ProjectCallout').with_foreign_key(:project_id) }
# GitLab Pages
it { is_expected.to have_many(:pages_domains) }

View File

@ -137,6 +137,7 @@ RSpec.describe User do
it { is_expected.to have_many(:callouts).class_name('Users::Callout') }
it { is_expected.to have_many(:group_callouts).class_name('Users::GroupCallout') }
it { is_expected.to have_many(:namespace_callouts).class_name('Users::NamespaceCallout') }
it { is_expected.to have_many(:project_callouts).class_name('Users::ProjectCallout') }
describe '#user_detail' do
it 'does not persist `user_detail` by default' do
@ -6671,6 +6672,40 @@ RSpec.describe User do
end
end
describe '#dismissed_callout_for_project?' do
let_it_be(:user, refind: true) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:feature_name) { Users::ProjectCallout.feature_names.each_key.first }
context 'when no callout dismissal record exists' do
it 'returns false when no ignore_dismissal_earlier_than provided' do
expect(user.dismissed_callout_for_project?(feature_name: feature_name, project: project)).to eq false
end
end
context 'when dismissed callout exists' do
before_all do
create(:project_callout,
user: user,
project_id: project.id,
feature_name: feature_name,
dismissed_at: 4.months.ago)
end
it 'returns true when no ignore_dismissal_earlier_than provided' do
expect(user.dismissed_callout_for_project?(feature_name: feature_name, project: project)).to eq true
end
it 'returns true when ignore_dismissal_earlier_than is earlier than dismissed_at' do
expect(user.dismissed_callout_for_project?(feature_name: feature_name, project: project, ignore_dismissal_earlier_than: 6.months.ago)).to eq true
end
it 'returns false when ignore_dismissal_earlier_than is later than dismissed_at' do
expect(user.dismissed_callout_for_project?(feature_name: feature_name, project: project, ignore_dismissal_earlier_than: 3.months.ago)).to eq false
end
end
end
describe '#find_or_initialize_group_callout' do
let_it_be(:user, refind: true) { create(:user) }
let_it_be(:group) { create(:group) }
@ -6715,6 +6750,50 @@ RSpec.describe User do
end
end
describe '#find_or_initialize_project_callout' do
let_it_be(:user, refind: true) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:feature_name) { Users::ProjectCallout.feature_names.each_key.first }
subject(:callout_with_source) do
user.find_or_initialize_project_callout(feature_name, project.id)
end
context 'when callout exists' do
let!(:callout) do
create(:project_callout, user: user, feature_name: feature_name, project_id: project.id)
end
it 'returns existing callout' do
expect(callout_with_source).to eq(callout)
end
end
context 'when callout does not exist' do
context 'when feature name is valid' do
it 'initializes a new callout' do
expect(callout_with_source).to be_a_new(Users::ProjectCallout)
end
it 'is valid' do
expect(callout_with_source).to be_valid
end
end
context 'when feature name is not valid' do
let(:feature_name) { 'notvalid' }
it 'initializes a new callout' do
expect(callout_with_source).to be_a_new(Users::ProjectCallout)
end
it 'is not valid' do
expect(callout_with_source).not_to be_valid
end
end
end
end
describe '#hook_attrs' do
let(:user) { create(:user) }
let(:user_attributes) do

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Users::ProjectCallout do
let_it_be(:user) { create_default(:user) }
let_it_be(:project) { create_default(:project) }
let_it_be(:callout) { create(:project_callout) }
it_behaves_like 'having unique enum values'
describe 'relationships' do
it { is_expected.to belong_to(:project) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:feature_name) }
it {
is_expected.to validate_uniqueness_of(:feature_name).scoped_to(:user_id, :project_id).ignoring_case_sensitivity
}
end
end

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Project callouts' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
before do
sign_in(user)
end
describe 'POST /-/users/project_callouts' do
let(:params) { { feature_name: feature_name, project_id: project.id } }
subject { post project_callouts_path, params: params, headers: { 'ACCEPT' => 'application/json' } }
context 'with valid feature name and project' do
let(:feature_name) { Users::ProjectCallout.feature_names.each_key.first }
context 'when callout entry does not exist' do
it 'creates a callout entry with dismissed state' do
expect { subject }.to change { Users::ProjectCallout.count }.by(1)
end
it 'returns success' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'when callout entry already exists' do
let!(:callout) do
create(:project_callout,
feature_name: Users::ProjectCallout.feature_names.each_key.first,
user: user,
project: project)
end
it 'returns success', :aggregate_failures do
expect { subject }.not_to change { Users::ProjectCallout.count }
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'with invalid feature name' do
let(:feature_name) { 'bogus_feature_name' }
it 'returns bad request' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
end

View File

@ -65,13 +65,19 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process' do
## Strong
This example doesn't have an extension after the `example` keyword, so its
`source_specification` will be `commonmark`.
```````````````````````````````` example
__bold__
.
<p><strong>bold</strong></p>
````````````````````````````````
```````````````````````````````` example strong
This example has an extension after the `example` keyword, so its
`source_specification` will be `github`.
```````````````````````````````` example some_extension_name
__bold with more text__
.
<p><strong>bold with more text</strong></p>
@ -132,6 +138,10 @@ RSpec.describe Glfm::UpdateExampleSnapshots, '#process' do
## Strong but with HTML
This example has the `gitlab` keyword after the `example` keyword, so its
`source_specification` will be `gitlab`.
```````````````````````````````` example gitlab strong
<strong>
bold

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Users::DismissProjectCalloutService do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:params) { { feature_name: feature_name, project_id: project.id } }
let(:feature_name) { Users::ProjectCallout.feature_names.each_key.first }
subject(:execute) do
described_class.new(
container: nil, current_user: user, params: params
).execute
end
it_behaves_like 'dismissing user callout', Users::ProjectCallout
it 'sets the project_id' do
expect(execute.project_id).to eq(project.id)
end
end
end