Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-06-02 03:07:46 +00:00
parent 3ae3a2c23f
commit 34beb8cec3
36 changed files with 295 additions and 134 deletions

View File

@ -451,7 +451,7 @@ lib/gitlab/checks/**
/doc/administration/monitoring/prometheus/index.md @axil
/doc/administration/monitoring/prometheus/pgbouncer_exporter.md @aqualls
/doc/administration/monitoring/prometheus/postgres_exporter.md @aqualls
/doc/administration/monitoring/prometheus/registry_exporter.md @marcel.amirault
/doc/administration/monitoring/prometheus/registry_exporter.md @phillipwells
/doc/administration/monitoring/prometheus/web_exporter.md @jglassman1
/doc/administration/nfs.md @axil
/doc/administration/object_storage.md @axil
@ -461,6 +461,7 @@ lib/gitlab/checks/**
/doc/administration/operations/moving_repositories.md @eread
/doc/administration/package_information/ @axil
/doc/administration/packages/ @marcel.amirault
/doc/administration/packages/index.md @phillipwells
/doc/administration/polling.md @axil
/doc/administration/postgresql/ @aqualls
/doc/administration/postgresql/multiple_databases.md @lciutacu
@ -583,8 +584,8 @@ lib/gitlab/checks/**
/doc/api/notification_settings.md @msedlakjakubowski
/doc/api/oauth2.md @jglassman1
/doc/api/openapi/ @eread @ashrafkhamis
/doc/api/packages.md @marcel.amirault
/doc/api/packages/ @marcel.amirault
/doc/api/packages.md @phillipwells
/doc/api/packages/ @phillipwells
/doc/api/personal_access_tokens.md @eread
/doc/api/pipeline_schedules.md @marcel.amirault
/doc/api/pipeline_triggers.md @marcel.amirault
@ -657,6 +658,7 @@ lib/gitlab/checks/**
/doc/ci/docker/using_docker_images.md @fneill
/doc/ci/environments/ @phillipwells
/doc/ci/examples/deployment/ @phillipwells
/doc/ci/examples/semantic-release.md @phillipwells
/doc/ci/interactive_web_terminal/ @fneill
/doc/ci/large_repositories/ @fneill
/doc/ci/resource_groups/ @phillipwells
@ -730,7 +732,9 @@ lib/gitlab/checks/**
/doc/development/navigation_sidebar.md @sselhorn
/doc/development/omnibus.md @axil
/doc/development/organization/ @lciutacu
/doc/development/packages/ @marcel.amirault
/doc/development/packages/ @phillipwells
/doc/development/packages/cleanup_policies.md @marcel.amirault
/doc/development/packages/dependency_proxy.md @marcel.amirault
/doc/development/permissions.md @jglassman1
/doc/development/policies.md @jglassman1
/doc/development/project_templates.md @aqualls @msedlakjakubowski
@ -833,7 +837,7 @@ lib/gitlab/checks/**
/doc/user/admin_area/settings/incident_management_rate_limits.md @msedlakjakubowski
/doc/user/admin_area/settings/index.md @aqualls @msedlakjakubowski
/doc/user/admin_area/settings/instance_template_repository.md @aqualls @msedlakjakubowski
/doc/user/admin_area/settings/package_registry_rate_limits.md @marcel.amirault
/doc/user/admin_area/settings/package_registry_rate_limits.md @phillipwells
/doc/user/admin_area/settings/project_integration_management.md @eread @ashrafkhamis
/doc/user/admin_area/settings/push_event_activities_limit.md @aqualls @msedlakjakubowski
/doc/user/admin_area/settings/rate_limit_on_issues_creation.md @msedlakjakubowski
@ -883,7 +887,10 @@ lib/gitlab/checks/**
/doc/user/okrs.md @msedlakjakubowski
/doc/user/operations_dashboard/ @phillipwells
/doc/user/organization/ @lciutacu
/doc/user/packages/ @marcel.amirault
/doc/user/packages/ @phillipwells
/doc/user/packages/container_registry/ @marcel.amirault
/doc/user/packages/dependency_proxy/ @marcel.amirault
/doc/user/packages/harbor_container_registry/ @marcel.amirault
/doc/user/permissions.md @jglassman1
/doc/user/product_analytics/ @lciutacu
/doc/user/profile/account/ @jglassman1

View File

@ -977,14 +977,6 @@ Layout/ArgumentAlignment:
- 'ee/app/models/incident_management/escalation_rule.rb'
- 'ee/app/models/integrations/github/status_notifier.rb'
- 'ee/app/models/status_page/project_setting.rb'
- 'ee/app/models/vulnerabilities/external_issue_link.rb'
- 'ee/app/models/vulnerabilities/feedback.rb'
- 'ee/app/models/vulnerabilities/finding/evidence.rb'
- 'ee/app/models/vulnerabilities/issue_link.rb'
- 'ee/app/models/vulnerabilities/merge_request_link.rb'
- 'ee/app/models/vulnerabilities/read.rb'
- 'ee/app/models/vulnerabilities/stat_diff.rb'
- 'ee/app/models/vulnerabilities/statistic.rb'
- 'ee/app/services/analytics/devops_adoption/enabled_namespaces/bulk_find_or_create_service.rb'
- 'ee/app/services/audit_events/streaming/event_type_filters/destroy_service.rb'
- 'ee/app/services/auto_merge/merge_train_service.rb'

View File

@ -586,8 +586,8 @@ RSpec/FactoryBot/AvoidCreate:
- 'spec/views/projects/hooks/edit.html.haml_spec.rb'
- 'spec/views/projects/hooks/index.html.haml_spec.rb'
- 'spec/views/projects/imports/new.html.haml_spec.rb'
- 'spec/views/projects/issues/_issue.html.haml_spec.rb'
- 'spec/views/projects/issues/_service_desk_info_content.html.haml_spec.rb'
- 'spec/views/projects/issues/service_desk/_issue.html.haml_spec.rb'
- 'spec/views/projects/issues/show.html.haml_spec.rb'
- 'spec/views/projects/jobs/_build.html.haml_spec.rb'
- 'spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb'

View File

@ -193,7 +193,7 @@ gem 'asciidoctor', '~> 2.0.18'
gem 'asciidoctor-include-ext', '~> 0.4.0', require: false
gem 'asciidoctor-plantuml', '~> 0.0.16'
gem 'asciidoctor-kroki', '~> 0.8.0', require: false
gem 'rouge', '~> 4.1.0'
gem 'rouge', '~> 4.1.2'
gem 'truncato', '~> 0.7.12'
gem 'nokogiri', '~> 1.15'

View File

@ -518,7 +518,7 @@
{"name":"rexml","version":"3.2.5","platform":"ruby","checksum":"a33c3bf95fda7983ec7f05054f3a985af41dbc25a0339843bd2479e93cabb123"},
{"name":"rinku","version":"2.0.0","platform":"ruby","checksum":"3e695aaf9f24baba3af45823b5c427b58a624582132f18482320e2737f9f8a85"},
{"name":"rotp","version":"6.2.0","platform":"ruby","checksum":"239a2eefba6f1bd4157b2c735d0f975598e0ef94823eea2f35d103d2e5cc0787"},
{"name":"rouge","version":"4.1.1","platform":"ruby","checksum":"41cc3ed28de7a9f5c0145bcdbeae8f5c16133065d570e21393aac935a235fd4b"},
{"name":"rouge","version":"4.1.2","platform":"ruby","checksum":"3b4ca60e4ac6e36be2deb0359cba04278ba15bdd2b1fbbb66bbc19cae517d55f"},
{"name":"rqrcode","version":"0.7.0","platform":"ruby","checksum":"8b3a5cba9cc199ba2d781a7c767cb55679f29a3621aa0506a799cec3760d16a1"},
{"name":"rqrcode-rails3","version":"0.1.7","platform":"ruby","checksum":"6f0582f26485123e5ed6f2a8a2871f00d86d353e0f58c8429a5a13212bcf48c4"},
{"name":"rspec","version":"3.12.0","platform":"ruby","checksum":"ccc41799a43509dc0be84070e3f0410ac95cbd480ae7b6c245543eb64162399c"},

View File

@ -1270,7 +1270,7 @@ GEM
rexml (3.2.5)
rinku (2.0.0)
rotp (6.2.0)
rouge (4.1.1)
rouge (4.1.2)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@ -1885,7 +1885,7 @@ DEPENDENCIES
responders (~> 3.0)
retriable (~> 3.1.2)
rexml (~> 3.2.5)
rouge (~> 4.1.0)
rouge (~> 4.1.2)
rqrcode-rails3 (~> 0.1.7)
rspec-benchmark (~> 0.6.0)
rspec-parameterized (~> 1.0)

View File

@ -4,7 +4,7 @@ import { ARCHIVE_FILE_TYPE, METADATA_FILE_TYPE, JOB_STATUS_GROUP_SUCCESS } from
export const totalArtifactsSizeForJob = (job) =>
numberToHumanSize(
job.artifacts.nodes
.map((artifact) => artifact.size)
.map((artifact) => Number(artifact.size))
.reduce((total, artifact) => total + artifact, 0),
);

View File

@ -82,7 +82,15 @@ export default {
/>
<div>
<p class="gl-font-weight-bold gl-mb-0" :data-testid="`${item.storageType.id}-name`">
{{ item.storageType.name }}
<gl-link
v-if="item.storageType.detailsPath && item.value"
:data-testid="`${item.storageType.id}-details-link`"
:href="item.storageType.detailsPath"
>{{ item.storageType.name }}</gl-link
>
<template v-else>
{{ item.storageType.name }}
</template>
<gl-link
v-if="item.storageType.helpPath"
:href="item.storageType.helpPath"

View File

@ -14,10 +14,10 @@ export default {
iconName(storageTypeName) {
const defaultStorageTypeIcon = 'disk';
const storageTypeIconMap = {
lfsObjectsSize: 'doc-image',
snippetsSize: 'snippet',
repositorySize: 'infrastructure-registry',
packagesSize: 'package',
lfsObjects: 'doc-image',
snippets: 'snippet',
repository: 'infrastructure-registry',
packages: 'package',
};
return storageTypeIconMap[`${storageTypeName}`] ?? defaultStorageTypeIcon;

View File

@ -1,11 +1,10 @@
<script>
import { numberToHumanSize } from '~/lib/utils/number_utils';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { PROJECT_STORAGE_TYPES } from '../constants';
import { descendingStorageUsageSort } from '../utils';
export default {
mixins: [glFeatureFlagMixin()],
name: 'UsageGraph',
props: {
rootStorageStatistics: {
required: true,
@ -36,49 +35,49 @@ export default {
return [
{
id: 'repositorySize',
id: 'repository',
style: this.usageStyle(this.barRatio(repositorySize)),
class: 'gl-bg-data-viz-blue-500',
size: repositorySize,
},
{
id: 'lfsObjectsSize',
id: 'lfsObjects',
style: this.usageStyle(this.barRatio(lfsObjectsSize)),
class: 'gl-bg-data-viz-orange-600',
size: lfsObjectsSize,
},
{
id: 'packagesSize',
id: 'packages',
style: this.usageStyle(this.barRatio(packagesSize)),
class: 'gl-bg-data-viz-aqua-500',
size: packagesSize,
},
{
id: 'containerRegistrySize',
id: 'containerRegistry',
style: this.usageStyle(this.barRatio(containerRegistrySize)),
class: 'gl-bg-data-viz-aqua-800',
size: containerRegistrySize,
},
{
id: 'buildArtifactsSize',
id: 'buildArtifacts',
style: this.usageStyle(this.barRatio(buildArtifactsSize)),
class: 'gl-bg-data-viz-green-500',
size: buildArtifactsSize,
},
{
id: 'pipelineArtifactsSize',
id: 'pipelineArtifacts',
style: this.usageStyle(this.barRatio(pipelineArtifactsSize)),
class: 'gl-bg-data-viz-green-800',
size: pipelineArtifactsSize,
},
{
id: 'wikiSize',
id: 'wiki',
style: this.usageStyle(this.barRatio(wikiSize)),
class: 'gl-bg-data-viz-magenta-500',
size: wikiSize,
},
{
id: 'snippetsSize',
id: 'snippets',
style: this.usageStyle(this.barRatio(snippetsSize)),
class: 'gl-bg-data-viz-orange-800',
size: snippetsSize,

View File

@ -26,44 +26,44 @@ export const PROJECT_TABLE_LABEL_USAGE = s__('UsageQuota|Usage');
export const PROJECT_STORAGE_TYPES = [
{
id: 'containerRegistrySize',
id: 'containerRegistry',
name: __('Container Registry'),
description: s__(
'UsageQuota|Gitlab-integrated Docker Container Registry for storing Docker Images.',
),
},
{
id: 'buildArtifactsSize',
id: 'buildArtifacts',
name: __('Job artifacts'),
description: s__('UsageQuota|Job artifacts created by CI/CD.'),
},
{
id: 'pipelineArtifactsSize',
id: 'pipelineArtifacts',
name: __('Pipeline artifacts'),
description: s__('UsageQuota|Pipeline artifacts created by CI/CD.'),
},
{
id: 'lfsObjectsSize',
id: 'lfsObjects',
name: __('LFS'),
description: s__('UsageQuota|Audio samples, videos, datasets, and graphics.'),
},
{
id: 'packagesSize',
id: 'packages',
name: __('Packages'),
description: s__('UsageQuota|Code packages and container images.'),
},
{
id: 'repositorySize',
id: 'repository',
name: __('Repository'),
description: s__('UsageQuota|Git repository.'),
},
{
id: 'snippetsSize',
id: 'snippets',
name: __('Snippets'),
description: s__('UsageQuota|Shared bits of code and text.'),
},
{
id: 'wikiSize',
id: 'wiki',
name: __('Wiki'),
description: s__('UsageQuota|Wiki content.'),
},

View File

@ -1,6 +1,14 @@
query getProjectStorageStatistics($fullPath: ID!) {
project(fullPath: $fullPath) {
id
statisticsDetailsPaths {
containerRegistry
buildArtifacts
packages
repository
snippets
wiki
}
statistics {
containerRegistrySize
buildArtifactsSize

View File

@ -1,17 +1,23 @@
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { PROJECT_STORAGE_TYPES } from './constants';
export const getStorageTypesFromProjectStatistics = (projectStatistics, helpLinks = {}) =>
export const getStorageTypesFromProjectStatistics = (
projectStatistics,
helpLinks = {},
statisticsDetailsPaths = {},
) =>
PROJECT_STORAGE_TYPES.reduce((types, currentType) => {
const helpPathKey = currentType.id.replace(`Size`, ``);
const helpPath = helpLinks[helpPathKey];
const helpPath = helpLinks[currentType.id];
const value = projectStatistics[`${currentType.id}Size`];
const detailsPath = statisticsDetailsPaths[currentType.id];
return types.concat({
storageType: {
...currentType,
helpPath,
detailsPath,
},
value: projectStatistics[currentType.id],
value,
});
}, []);
@ -27,7 +33,11 @@ export const parseGetProjectStorageResults = (data, helpLinks) => {
return {};
}
const { storageSize } = projectStatistics;
const storageTypes = getStorageTypesFromProjectStatistics(projectStatistics, helpLinks);
const storageTypes = getStorageTypesFromProjectStatistics(
projectStatistics,
helpLinks,
data?.project?.statisticsDetailsPaths,
);
return {
storage: {

View File

@ -79,10 +79,6 @@ class PersonalAccessToken < ApplicationRecord
fuzzy_search(query, [:name])
end
def project_access_token?
user&.project_bot?
end
protected
def validate_scopes

View File

@ -17,6 +17,8 @@ module ResourceAccessTokens
access_level = params[:access_level] || Gitlab::Access::MAINTAINER
return error("Could not provision owner access to project access token") if do_not_allow_owner_access_level_for_project_bot?(access_level)
return error(s_('AccessTokens|Access token limit reached')) if reached_access_token_limit?
user = create_user
return error(user.errors.full_messages.to_sentence) unless user.persisted?
@ -45,6 +47,10 @@ module ResourceAccessTokens
attr_reader :resource_type, :resource
def reached_access_token_limit?
false
end
def username_and_email_generator
Gitlab::Utils::UsernameAndEmailGenerator.new(
username_prefix: "#{resource_type}_#{resource.id}_bot",

View File

@ -8,7 +8,7 @@
- if any_form_based_providers_enabled?
= render 'devise/shared/tabs_ldap', show_password_form: allow_admin_mode_password_authentication_for_web?, render_signup_link: false
- else
= render 'devise/shared/tab_single', tab_title: page_title
= render 'devise/shared/tab_single', tab_title: page_title if Feature.disabled?(:restyle_login_page, @project)
.tab-content
- if allow_admin_mode_password_authentication_for_web? || ldap_sign_in_enabled? || crowd_enabled?
= render 'admin/sessions/signin_box'

View File

@ -5,11 +5,10 @@
.col-md-5.new-session-forms-container
.login-page
#signin-container{ class: ('borderless' if Feature.enabled?(:restyle_login_page, @project)) }
= render 'devise/shared/tab_single', tab_title: _('Enter admin mode')
.tab-content
.login-box.tab-pane.gl-p-5.active{ id: 'login-pane', role: 'tabpanel' }
.login-body
- if current_user.two_factor_enabled?
= render 'admin/sessions/two_factor_otp'
- if current_user.two_factor_webauthn_enabled?
= render 'authentication/authenticate', render_remember_me: false, target_path: admin_session_path
= render 'devise/shared/tab_single', tab_title: _('Enter admin mode') if Feature.disabled?(:restyle_login_page, @project)
.login-box.gl-p-5
.login-body
- if current_user.two_factor_enabled?
= render 'admin/sessions/two_factor_otp'
- if current_user.two_factor_webauthn_enabled?
= render 'authentication/authenticate', render_remember_me: false, target_path: admin_session_path

View File

@ -4,7 +4,7 @@
- if has_start_trial?
= render_if_exists "dashboard/projects/blank_state_ee_trial"
= link_to new_project_path, class: link_classes do
= link_to new_project_path, class: link_classes, data: { qa_selector: 'new_project_button' } do
.blank-state-icon
= custom_icon("add_new_project", size: 50)
.blank-state-body.gl-sm-pl-6

View File

@ -2,7 +2,7 @@
.gl-display-flex.gl-flex-wrap.gl-justify-content-space-between
- if current_user.can_create_project?
= link_to new_project_path, class: link_classes do
= link_to new_project_path, class: link_classes, data: { qa_selector: 'new_project_button' } do
.blank-state-icon
= custom_icon("add_new_project", size: 50)
.blank-state-body.gl-sm-pl-6

View File

@ -0,0 +1,14 @@
- type = local_assigns.fetch(:type)
= render 'shared/access_tokens/form',
ajax: true,
type: type,
path: project_settings_access_tokens_path(@project),
resource: @project,
token: @resource_access_token,
scopes: @scopes,
access_levels: ProjectMember.permissible_access_level_roles(current_user, @project),
default_access_level: Gitlab::Access::GUEST,
prefix: :resource_access_token,
description_prefix: :project_access_token,
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')

View File

@ -27,18 +27,8 @@
#js-new-access-token-app{ data: { access_token_type: type } }
- if current_user.can?(:create_resource_access_tokens, @project)
= render 'shared/access_tokens/form',
ajax: true,
type: type,
path: project_settings_access_tokens_path(@project),
resource: @project,
token: @resource_access_token,
scopes: @scopes,
access_levels: ProjectMember.permissible_access_level_roles(current_user, @project),
default_access_level: Gitlab::Access::GUEST,
prefix: :resource_access_token,
description_prefix: :project_access_token,
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')
= render_if_exists 'projects/settings/access_tokens/form',
type: type
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json, no_active_tokens_message: _('This project has no active access tokens.'), show_role: true
} }

View File

@ -0,0 +1,8 @@
---
name: normalize_links_for_sanitizing
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119040
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/413779
milestone: '16.1'
type: development
group: group::source code
default_enabled: false

View File

@ -31,7 +31,7 @@ See [Externalization for GitLab](externalization.md).
## Translate strings
The translation process is managed at [https://translate.gitlab.com](https://translate.gitlab.com)
The translation process is managed at [https://crowdin.com/project/gitlab-ee](https://crowdin.com/project/gitlab-ee)
using [Crowdin](https://crowdin.com/).
You must create a Crowdin account before you can submit translations. Once you are signed in, select
the language you wish to contribute translations to.

View File

@ -51,7 +51,9 @@ module Gitlab
begin
node[attr] = node[attr].strip
uri = Addressable::URI.parse(node[attr])
uri = uri.normalize if ::Feature.enabled?(:normalize_links_for_sanitizing)
next unless uri.scheme
next if safe_protocol?(uri.scheme)

View File

@ -57,7 +57,7 @@ namespace :tw do
# CodeOwnerRule.new('Observability', ''),
CodeOwnerRule.new('Optimize', '@lciutacu'),
CodeOwnerRule.new('Organization', '@lciutacu'),
CodeOwnerRule.new('Package Registry', '@marcel.amirault'),
CodeOwnerRule.new('Package Registry', '@phillipwells'),
CodeOwnerRule.new('Pipeline Authoring', '@marcel.amirault'),
CodeOwnerRule.new('Pipeline Execution', '@marcel.amirault'),
CodeOwnerRule.new('Pipeline Security', '@marcel.amirault'),

View File

@ -1868,9 +1868,6 @@ msgstr ""
msgid "AI-generated test file"
msgstr ""
msgid "AISummary|Experiment"
msgstr ""
msgid "AISummary|Generates a summary of all comments"
msgstr ""
@ -1898,9 +1895,6 @@ msgstr ""
msgid "AI|Code Explanation"
msgstr ""
msgid "AI|Describe the issue"
msgstr ""
msgid "AI|Enabling these features is your acceptance of the %{link_start}GitLab Testing Agreement%{link_end}."
msgstr ""
@ -1916,7 +1910,7 @@ msgstr ""
msgid "AI|Features that use third-party AI services require transmission of data, including personal data."
msgstr ""
msgid "AI|For example: It should be possible to forecast into the future using our value stream analytics charts. This would allow organizations to better understand how they are trending on important metrics."
msgid "AI|For example: Organizations should be able to forecast into the future by using value stream analytics charts. This feature would help them understand how their metrics are trending."
msgstr ""
msgid "AI|Helpful"
@ -1925,6 +1919,9 @@ msgstr ""
msgid "AI|I don't see how I can help. Please give better instructions!"
msgstr ""
msgid "AI|Populate issue description"
msgstr ""
msgid "AI|Responses generated by AI"
msgstr ""
@ -1958,6 +1955,9 @@ msgstr ""
msgid "AI|What does the selected code mean?"
msgstr ""
msgid "AI|Write a brief description and have AI fill in the details."
msgstr ""
msgid "AI|Write a summary to fill out the selected issue template"
msgstr ""
@ -2366,6 +2366,9 @@ msgstr ""
msgid "AccessTokens|Access Tokens"
msgstr ""
msgid "AccessTokens|Access token limit reached"
msgstr ""
msgid "AccessTokens|Are you sure?"
msgstr ""
@ -2423,6 +2426,9 @@ msgstr ""
msgid "AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API."
msgstr ""
msgid "AccessTokens|You can only have one active project access token with a trial license. You cannot generate a new token until the existing token is deleted, or you upgrade your subscription."
msgstr ""
msgid "AccessTokens|Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar. It is visible in those feed URLs."
msgstr ""
@ -40108,9 +40114,18 @@ msgstr ""
msgid "ScanResultPolicy|Choose criteria type"
msgstr ""
msgid "ScanResultPolicy|Except"
msgstr ""
msgid "ScanResultPolicy|License is:"
msgstr ""
msgid "ScanResultPolicy|License scanning allows only one criteria: Status"
msgstr ""
msgid "ScanResultPolicy|Matching"
msgstr ""
msgid "ScanResultPolicy|Maximum number of severity-criteria is one"
msgstr ""
@ -40132,6 +40147,9 @@ msgstr ""
msgid "ScanResultPolicy|Select a scan type before adding criteria"
msgstr ""
msgid "ScanResultPolicy|Select license types"
msgstr ""
msgid "ScanResultPolicy|Severity is:"
msgstr ""
@ -40141,24 +40159,15 @@ msgstr ""
msgid "ScanResultPolicy|When %{scanType} %{scanners} runs against the %{branches} and find(s) %{vulnerabilitiesNumber} %{boldDescription} of the following criteria:"
msgstr ""
msgid "ScanResultPolicy|When %{scanType} find any license %{matchType} %{licenseType} in an open merge request targeting the %{branches} and the licences match all of the following criteria"
msgid "ScanResultPolicy|When %{scanType} in an open merge request targeting the %{branches} and the licenses match all of the following criteria:"
msgstr ""
msgid "ScanResultPolicy|When %{scanners} find scanner specified conditions in an open merge request targeting the %{branches} and match %{boldDescription} of the following criteria"
msgstr ""
msgid "ScanResultPolicy|except"
msgstr ""
msgid "ScanResultPolicy|license status"
msgstr ""
msgid "ScanResultPolicy|license type"
msgstr ""
msgid "ScanResultPolicy|matching"
msgstr ""
msgid "ScanResultPolicy|matching type"
msgstr ""

View File

@ -17,6 +17,14 @@ module QA
element :new_project_button
end
view 'app/views/dashboard/projects/_blank_state_welcome.html.haml' do
element :new_project_button
end
view 'app/views/dashboard/projects/_blank_state_admin_welcome.html.haml' do
element :new_project_button
end
def has_project_with_access_role?(project_name, access_role)
within_element(:project_content, text: project_name) do
has_element?(:user_role_content, text: access_role)

View File

@ -0,0 +1,16 @@
import getJobArtifactsResponse from 'test_fixtures/graphql/ci/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { totalArtifactsSizeForJob } from '~/ci/artifacts/utils';
const job = getJobArtifactsResponse.data.project.jobs.nodes[0];
const artifacts = job.artifacts.nodes;
describe('totalArtifactsSizeForJob', () => {
it('adds artifact sizes together', () => {
expect(totalArtifactsSizeForJob(job)).toBe(
numberToHumanSize(
Number(artifacts[0].size) + Number(artifacts[1].size) + Number(artifacts[2].size),
),
);
});
});

View File

@ -26,7 +26,7 @@ describe('ProjectStorageDetail', () => {
);
};
const generateStorageType = (id = 'buildArtifactsSize') => {
const generateStorageType = (id = 'buildArtifacts') => {
return {
storageType: {
id,
@ -56,7 +56,7 @@ describe('ProjectStorageDetail', () => {
expect(wrapper.findByTestId(`${id}-description`).text()).toBe(description);
expect(wrapper.findByTestId(`${id}-icon`).props('name')).toBe(id);
expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe(
projectHelpLinks[id.replace(`Size`, ``)],
projectHelpLinks[id],
);
},
);
@ -74,6 +74,14 @@ describe('ProjectStorageDetail', () => {
});
});
describe('with details links', () => {
it.each(storageTypes)('each $storageType.id', (item) => {
const shouldExist = Boolean(item.storageType.detailsPath && item.value);
const detailsLink = wrapper.findByTestId(`${item.storageType.id}-details-link`);
expect(detailsLink.exists()).toBe(shouldExist);
});
});
describe('without storage types', () => {
beforeEach(() => {
createComponent({ storageTypes: [] });

View File

@ -18,11 +18,11 @@ describe('StorageTypeIcon', () => {
describe('rendering icon', () => {
it.each`
expected | provided
${'doc-image'} | ${'lfsObjectsSize'}
${'snippet'} | ${'snippetsSize'}
${'infrastructure-registry'} | ${'repositorySize'}
${'package'} | ${'packagesSize'}
${'disk'} | ${'wikiSize'}
${'doc-image'} | ${'lfsObjects'}
${'snippet'} | ${'snippets'}
${'infrastructure-registry'} | ${'repository'}
${'package'} | ${'packages'}
${'disk'} | ${'wiki'}
${'disk'} | ${'anything-else'}
`(
'renders icon with name of $expected when name prop is $provided',

View File

@ -9,25 +9,27 @@ export const projectData = {
storageTypes: [
{
storageType: {
id: 'containerRegistrySize',
id: 'containerRegistry',
name: 'Container Registry',
description: 'Gitlab-integrated Docker Container Registry for storing Docker Images.',
helpPath: '/container_registry',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/container_registry',
},
value: 3_900_000,
value: 3900000,
},
{
storageType: {
id: 'buildArtifactsSize',
id: 'buildArtifacts',
name: 'Job artifacts',
description: 'Job artifacts created by CI/CD.',
helpPath: '/build-artifacts',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/artifacts',
},
value: 400000,
},
{
storageType: {
id: 'pipelineArtifactsSize',
id: 'pipelineArtifacts',
name: 'Pipeline artifacts',
description: 'Pipeline artifacts created by CI/CD.',
helpPath: '/pipeline-artifacts',
@ -36,7 +38,7 @@ export const projectData = {
},
{
storageType: {
id: 'lfsObjectsSize',
id: 'lfsObjects',
name: 'LFS',
description: 'Audio samples, videos, datasets, and graphics.',
helpPath: '/lsf-objects',
@ -45,37 +47,41 @@ export const projectData = {
},
{
storageType: {
id: 'packagesSize',
id: 'packages',
name: 'Packages',
description: 'Code packages and container images.',
helpPath: '/packages',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/packages',
},
value: 3800000,
},
{
storageType: {
id: 'repositorySize',
id: 'repository',
name: 'Repository',
description: 'Git repository.',
helpPath: '/repository',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/tree/master',
},
value: 3900000,
},
{
storageType: {
id: 'snippetsSize',
id: 'snippets',
name: 'Snippets',
description: 'Shared bits of code and text.',
helpPath: '/snippets',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/snippets',
},
value: 0,
},
{
storageType: {
id: 'wikiSize',
id: 'wiki',
name: 'Wiki',
description: 'Wiki content.',
helpPath: '/wiki',
detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/wikis/pages',
},
value: 300000,
},

View File

@ -12,7 +12,10 @@ import {
} from './mock_data';
describe('getStorageTypesFromProjectStatistics', () => {
const projectStatistics = mockGetProjectStorageStatisticsGraphQLResponse.data.project.statistics;
const {
statistics: projectStatistics,
statisticsDetailsPaths,
} = mockGetProjectStorageStatisticsGraphQLResponse.data.project;
describe('matches project statistics value with matching storage type', () => {
const typesWithStats = getStorageTypesFromProjectStatistics(projectStatistics);
@ -22,29 +25,39 @@ describe('getStorageTypesFromProjectStatistics', () => {
storageType: expect.objectContaining({
id,
}),
value: projectStatistics[id],
value: projectStatistics[`${id}Size`],
});
});
});
it('adds helpPath to a relevant type', () => {
const trimTypeId = (id) => id.replace('Size', '');
const helpLinks = PROJECT_STORAGE_TYPES.reduce((acc, { id }) => {
const key = trimTypeId(id);
return {
...acc,
[key]: `url://${id}`,
[id]: `url://${id}`,
};
}, {});
const typesWithStats = getStorageTypesFromProjectStatistics(projectStatistics, helpLinks);
typesWithStats.forEach((type) => {
const key = trimTypeId(type.storageType.id);
const key = type.storageType.id;
expect(type.storageType.helpPath).toBe(helpLinks[key]);
});
});
it('adds details page path', () => {
const typesWithStats = getStorageTypesFromProjectStatistics(
projectStatistics,
{},
statisticsDetailsPaths,
);
typesWithStats.forEach((type) => {
expect(type.storageType.detailsPath).toBe(statisticsDetailsPaths[type.storageType.id]);
});
});
});
describe('parseGetProjectStorageResults', () => {
it('parses project statistics correctly', () => {
expect(

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'fast_spec_helper'
# TODO: change to fast_spec_helper in scope of https://gitlab.com/gitlab-org/gitlab/-/issues/413779
require 'spec_helper'
require 'html/pipeline'
require 'addressable'
@ -27,9 +28,13 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
" &#14; javascript:"
]
invalid_schemes.each do |scheme|
context "with the scheme: #{scheme}" do
describe "#remove_unsafe_links" do
describe "#remove_unsafe_links" do
subject { object.remove_unsafe_links(env, remove_invalid_links: true) }
let(:env) { { node: node } }
invalid_schemes.each do |scheme|
context "with the scheme: #{scheme}" do
tags = {
a: {
doc: HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>"),
@ -55,19 +60,67 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
tags.each do |tag, opts|
context "<#{tag}> tags" do
it "removes the unsafe link" do
node = opts[:node_to_check].call(opts[:doc])
let(:node) { opts[:node_to_check].call(opts[:doc]) }
expect { object.remove_unsafe_links({ node: node }, remove_invalid_links: true) }
.to change { node[opts[:attr]] }
it "removes the unsafe link" do
expect { subject }.to change { node[opts[:attr]] }
expect(node[opts[:attr]]).to be_blank
end
end
end
end
end
describe "#safe_protocol?" do
context 'when URI is valid' do
let(:doc) { HTML::Pipeline.parse("<a href='http://example.com'>foo</a>") }
let(:node) { doc.children.first }
it 'does not remove it' do
subject
expect(node[:href]).to eq('http://example.com')
end
end
context 'when URI is invalid' do
let(:doc) { HTML::Pipeline.parse("<a href='http://example:wrong_port.com'>foo</a>") }
let(:node) { doc.children.first }
it 'removes the link' do
subject
expect(node[:href]).to be_nil
end
end
context 'when URI is encoded but still invalid' do
let(:doc) { HTML::Pipeline.parse("<a href='http://example%EF%BC%9A%E7%BD%91'>foo</a>") }
let(:node) { doc.children.first }
it 'removes the link' do
subject
expect(node[:href]).to be_nil
end
context 'when feature flag "normalize_links_for_sanitizing" is disabled' do
before do
stub_feature_flags(normalize_links_for_sanitizing: false)
end
it 'does not remove it' do
subject
expect(node[:href]).to eq('http://example%EF%BC%9A%E7%BD%91')
end
end
end
end
describe "#safe_protocol?" do
invalid_schemes.each do |scheme|
context "with the scheme: #{scheme}" do
let(:doc) { HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>") }
let(:node) { doc.children.first }
let(:uri) { Addressable::URI.parse(node['href']) }
@ -78,4 +131,14 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
end
end
end
describe '#sanitize_unsafe_links' do
let(:env) { { node: 'node' } }
it 'makes a call to #remove_unsafe_links_method' do
expect(object).to receive(:remove_unsafe_links).with(env)
object.sanitize_unsafe_links(env)
end
end
end

View File

@ -19,8 +19,7 @@ RSpec.describe 'admin/sessions/new.html.haml' do
it 'shows enter password form' do
render
expect(rendered).to have_selector('[data-testid="sign-in-tab"]')
expect(rendered).to have_css('#login-pane.active')
expect(rendered).to have_css('.login-box')
expect(rendered).to have_selector('[data-testid="password-field"]')
end
@ -29,7 +28,7 @@ RSpec.describe 'admin/sessions/new.html.haml' do
render
expect(rendered).not_to have_css('#login-pane')
expect(rendered).not_to have_css('.login-box')
expect(rendered).to have_content _('No authentication methods configured.')
end
end

View File

@ -24,7 +24,7 @@ RSpec.describe 'admin/sessions/two_factor.html.haml' do
it 'shows enter otp form' do
render
expect(rendered).to have_css('#login-pane.active')
expect(rendered).to have_css('.login-box')
expect(rendered).to have_field('user[otp_attempt]')
end
end

View File

@ -14,7 +14,7 @@ RSpec.describe 'projects/issues/service_desk/_issue.html.haml', feature_category
describe 'timestamp', :freeze_time do
context 'when issue is open' do
let(:issue) { create(:issue, updated_at: 1.day.ago) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
let(:issue) { create(:issue, updated_at: 1.day.ago) }
it 'shows last updated date' do
expect(rendered).to have_content("updated #{format_timestamp(1.day.ago)}")
@ -22,7 +22,7 @@ RSpec.describe 'projects/issues/service_desk/_issue.html.haml', feature_category
end
context 'when issue is closed' do
let(:issue) { create(:issue, :closed, closed_at: 2.days.ago, updated_at: 1.day.ago) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
let(:issue) { create(:issue, :closed, closed_at: 2.days.ago, updated_at: 1.day.ago) }
it 'shows closed date' do
expect(rendered).to have_content("closed #{format_timestamp(2.days.ago)}")
@ -30,7 +30,7 @@ RSpec.describe 'projects/issues/service_desk/_issue.html.haml', feature_category
end
context 'when issue is closed but closed_at is empty' do
let(:issue) { create(:issue, :closed, closed_at: nil, updated_at: 1.day.ago) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
let(:issue) { create(:issue, :closed, closed_at: nil, updated_at: 1.day.ago) }
it 'shows last updated date' do
expect(rendered).to have_content("updated #{format_timestamp(1.day.ago)}")
@ -40,7 +40,7 @@ RSpec.describe 'projects/issues/service_desk/_issue.html.haml', feature_category
context 'when issue is service desk issue' do
let_it_be(:email) { 'user@example.com' }
let_it_be(:obfuscated_email) { 'us*****@e*****.c**' }
let_it_be(:issue) { create(:issue, author: User.support_bot, service_desk_reply_to: email) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
let_it_be(:issue) { create(:issue, author: User.support_bot, service_desk_reply_to: email) }
context 'with anonymous user' do
it 'obfuscates service_desk_reply_to email for anonymous user' do
@ -49,7 +49,7 @@ RSpec.describe 'projects/issues/service_desk/_issue.html.haml', feature_category
end
context 'with signed in user' do
let_it_be(:user) { create(:user) } # rubocop:disable RSpec/FactoryBot/AvoidCreate
let_it_be(:user) { create(:user) }
before do
allow(view).to receive(:current_user).and_return(user)