提交 2c171fdd 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 c70359a0
......@@ -314,11 +314,6 @@ export default {
{{ __('An error occurred while loading designs. Please try again.') }}
</gl-alert>
<ol v-else class="list-unstyled row">
<span
v-if="isDesignListEmpty && !allVersions.length"
class="gl-font-weight-bold gl-font-weight-bold gl-ml-5 gl-mb-4"
>{{ s__('DesignManagement|Designs') }}</span
>
<li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper">
<design-dropzone
:class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }"
......
......@@ -3,7 +3,7 @@
/* global ListLabel */
import $ from 'jquery';
import { difference, isEqual, escape, sortBy, template } from 'lodash';
import { difference, isEqual, escape, sortBy, template, union } from 'lodash';
import { sprintf, s__, __ } from './locale';
import axios from './lib/utils/axios_utils';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
......@@ -560,15 +560,15 @@ export default class LabelsSelect {
IssuableBulkUpdateActions.willUpdateLabels = true;
}
// eslint-disable-next-line class-methods-use-this
setDropdownData($dropdown, isChecking, labelId) {
setDropdownData($dropdown, isMarking, labelId) {
let userCheckedIds = $dropdown.data('user-checked') || [];
let userUncheckedIds = $dropdown.data('user-unchecked') || [];
if (isChecking) {
userCheckedIds = userCheckedIds.concat(labelId);
if (isMarking) {
userCheckedIds = union(userCheckedIds, [labelId]);
userUncheckedIds = difference(userUncheckedIds, [labelId]);
} else {
userUncheckedIds = userUncheckedIds.concat(labelId);
userUncheckedIds = union(userUncheckedIds, [labelId]);
userCheckedIds = difference(userCheckedIds, [labelId]);
}
......
......@@ -11,7 +11,7 @@ module Mutations
argument :iid, GraphQL::STRING_TYPE,
required: true,
description: "The iid of the issue to mutate"
description: "The IID of the issue to mutate"
field :issue,
Types::IssueType,
......
# frozen_string_literal: true
module NamespaceStorageLimitAlertHelper
# Overridden in EE
def display_namespace_storage_limit_alert!
end
end
NamespaceStorageLimitAlertHelper.prepend_if_ee('EE::NamespaceStorageLimitAlertHelper')
......@@ -37,8 +37,8 @@
= message.target_path
%td
= message.broadcast_type.capitalize
%td.gl-white-space-nowrap
= link_to sprite_icon('pencil-square'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn'
= link_to sprite_icon('remove'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-danger'
%td.gl-white-space-nowrap.gl-display-flex
= link_to sprite_icon('pencil-square', css_class: 'gl-icon'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-icon gl-button'
= link_to sprite_icon('remove', css_class: 'gl-icon'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-icon gl-button btn-danger ml-2'
= paginate @broadcast_messages, theme: 'gitlab'
......@@ -6,8 +6,8 @@
%h3.page-title
= _('Group: %{group_name}') % { group_name: @group.full_name }
= link_to admin_group_edit_path(@group), class: "btn float-right", data: { qa_selector: 'edit_group_link' } do
%i.fa.fa-pencil-square-o
= link_to admin_group_edit_path(@group), class: "btn btn-default gl-button float-right", data: { qa_selector: 'edit_group_link' } do
= sprite_icon('pencil-square', css_class: 'gl-icon')
= _('Edit')
%hr
.row
......@@ -123,7 +123,9 @@
= _("<strong>%{group_name}</strong> group members").html_safe % { group_name: @group.name }
%span.badge.badge-pill= @group.members.size
.float-right
= link_to icon('pencil-square-o', text: _('Manage access')), group_group_members_path(@group), class: "btn btn-sm"
= link_to group_group_members_path(@group), class: 'btn btn-default gl-button btn-sm' do
= sprite_icon('pencil-square', css_class: 'gl-icon')
= _('Manage access')
%ul.content-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.card-footer
......
......@@ -6,8 +6,8 @@
.js-remove-member-modal
%h3.page-title
= _('Project: %{name}') % { name: @project.full_name }
= link_to edit_project_path(@project), class: "btn btn-nr float-right" do
%i.fa.fa-pencil-square-o
= link_to edit_project_path(@project), class: "btn btn-default gl-button float-right" do
= sprite_icon('pencil-square', css_class: 'gl-icon')
= _('Edit')
%hr
- if @project.last_repository_check_failed?
......@@ -178,8 +178,9 @@
= _('group members')
%span.badge.badge-pill= @group_members.size
.float-right
= link_to admin_group_path(@group), class: 'btn btn-sm' do
= icon('pencil-square-o', text: _('Manage access'))
= link_to admin_group_path(@group), class: 'btn btn-default gl-button btn-sm' do
= sprite_icon('pencil-square', css_class: 'gl-icon')
= _('Manage access')
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.card-footer
......@@ -193,7 +194,9 @@
= _('project members')
%span.badge.badge-pill= @project.users.size
.float-right
= link_to icon('pencil-square-o', text: _('Manage access')), project_project_members_path(@project), class: "btn btn-sm"
= link_to project_project_members_path(@project), class: 'btn btn-default gl-button btn-sm' do
= sprite_icon('pencil-square', css_class: 'gl-icon')
= _('Manage access')
%ul.content-list.project_members.members-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.card-footer
......
......@@ -12,10 +12,10 @@
.float-right
- if impersonation_enabled? && @user != current_user && @user.can?(:log_in)
= link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-nr btn-grouped btn-info", data: { qa_selector: 'impersonate_user_link' }
= link_to edit_admin_user_path(@user), class: "btn btn-nr btn-grouped" do
%i.fa.fa-pencil-square-o
Edit
= link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-info gl-button btn-grouped", data: { qa_selector: 'impersonate_user_link' }
= link_to edit_admin_user_path(@user), class: "btn btn-default gl-button btn-grouped" do
= sprite_icon('pencil-square', css_class: 'gl-icon')
= _('Edit')
%hr
%ul.nav-links.nav.nav-tabs
= nav_link(path: 'users#show') do
......
= content_for :flash_message do
= render_if_exists 'shared/shared_runners_minutes_limit', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
= render_if_exists 'shared/namespace_storage_limit_alert', namespace: @group, classes: [container_class, ("limit-container-width" unless fluid_layout)]
......@@ -15,6 +15,7 @@
= render "shared/ping_consent"
= render_account_recovery_regular_check
= render_if_exists "layouts/header/ee_subscribable_banner"
= render_if_exists "shared/namespace_storage_limit_alert"
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
.d-flex
......
......@@ -3,6 +3,7 @@
- header_title group_title(@group) unless header_title
- nav "group"
- display_subscription_banner!
- display_namespace_storage_limit_alert!
- @left_sidebar = true
- content_for :page_specific_javascripts do
......
......@@ -9,4 +9,3 @@
= render 'shared/auto_devops_implicitly_enabled_banner', project: project
= render_if_exists 'projects/above_size_limit_warning', project: project
= render_if_exists 'shared/shared_runners_minutes_limit', project: project, classes: [container_class, ("limit-container-width" unless fluid_layout)]
= render_if_exists 'shared/namespace_storage_limit_alert', namespace: project.namespace, classes: [container_class, ("limit-container-width" unless fluid_layout)]
- breadcrumb_title _("Details")
- page_title _("Projects")
- @content_class = "limit-container-width" unless fluid_layout
- display_namespace_storage_limit_alert!
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_path(@project, rss_url_options), title: "#{@project.name} activity")
......
---
title: Replace fa-pencil-square-o icons with GitLab SVG icons
merge_request: 36059
author:
type: other
---
title: 'UX Polish: Remove the header Designs on empty state'
merge_request: 37548
author:
type: fixed
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
content =<<eos
class Member < ActiveRecord::Base
include Notifiable
include Gitlab::Access
belongs_to :user
belongs_to :source, polymorphic: true
validates :user, presence: true
validates :source, presence: true
validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source" }
validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
scope :guests, -> { where(access_level: GUEST) }
scope :reporters, -> { where(access_level: REPORTER) }
scope :developers, -> { where(access_level: DEVELOPER) }
scope :maintainers, -> { where(access_level: MAINTAINER) }
scope :owners, -> { where(access_level: OWNER) }
SNIPPET_REPO_URL = "https://gitlab.com/gitlab-org/gitlab-snippet-test.git"
delegate :name, :username, :email, to: :user, prefix: true
end
eos
50.times do |i|
Gitlab::Seeder.quiet do
20.times do |i|
user = User.not_mass_generated.sample
PersonalSnippet.seed(:id, [{
id: i,
author_id: user.id,
user.snippets.create({
type: 'PersonalSnippet',
title: FFaker::Lorem.sentence(3),
file_name: FFaker::Internet.domain_word + '.rb',
file_name: 'file.rb',
visibility_level: Gitlab::VisibilityLevel.values.sample,
content: content,
}])
content: 'foo'
}).tap do |snippet|
unless snippet.repository_exists?
snippet.repository.import_repository(SNIPPET_REPO_URL)
end
snippet.track_snippet_repository(snippet.repository.storage)
snippet.statistics.refresh!
end
print('.')
end
......
......@@ -305,6 +305,12 @@ Select one node as a primary node.
CREATE EXTENSION pg_trgm;
```
1. Enable the `btree_gist` extension:
```shell
CREATE EXTENSION btree_gist;
```
1. Exit the database prompt by typing `\q` and Enter.
1. Verify the cluster is initialized with one node:
......@@ -736,9 +742,9 @@ consul['configuration'] = {
After deploying the configuration follow these steps:
1. On `10.6.0.31`, our primary database
1. On `10.6.0.31`, our primary database:
Enable the `pg_trgm` extension
Enable the `pg_trgm` and `btree_gist` extensions:
```shell
gitlab-psql -d gitlabhq_production
......@@ -746,33 +752,34 @@ After deploying the configuration follow these steps:
```shell
CREATE EXTENSION pg_trgm;
CREATE EXTENSION btree_gist;
```
1. On `10.6.0.32`, our first standby database
1. On `10.6.0.32`, our first standby database:
Make this node a standby of the primary
Make this node a standby of the primary:
```shell
gitlab-ctl repmgr standby setup 10.6.0.21
```
1. On `10.6.0.33`, our second standby database
1. On `10.6.0.33`, our second standby database:
Make this node a standby of the primary
Make this node a standby of the primary:
```shell
gitlab-ctl repmgr standby setup 10.6.0.21
```
1. On `10.6.0.41`, our application server
1. On `10.6.0.41`, our application server:
Set `gitlab-consul` user's PgBouncer password to `toomanysecrets`
Set `gitlab-consul` user's PgBouncer password to `toomanysecrets`:
```shell
gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
```
Run database migrations
Run database migrations:
```shell
gitlab-rake gitlab:db:configure
......@@ -1324,7 +1331,7 @@ You can switch an exiting database cluster to use Patroni instead of repmgr with
NOTE: **Note:**
Ensure that there is no `walsender` process running on the primary node.
`ps aux | grep walsender` must not show any running process.
`ps aux | grep walsender` must not show any running process.
1. On the primary node, [configure Patroni](#configuring-patroni-cluster). Remove `repmgr` and any other
repmgr-specific configuration. Also remove any configuration that is related to PostgreSQL replication.
......
......@@ -804,10 +804,11 @@ SSH into the **primary node**:
gitlab-psql -d gitlabhq_production
```
1. Enable the `pg_trgm` extension:
1. Enable the `pg_trgm` and `btree_gist` extensions:
```shell
CREATE EXTENSION pg_trgm;
CREATE EXTENSION btree_gist;
```
1. Exit the database prompt by typing `\q` and Enter.
......
......@@ -57,6 +57,7 @@ This section is for links to information elsewhere in the GitLab documentation.
- [GitLab database requirements](../../install/requirements.md#database) including
- Support for MySQL was removed in GitLab 12.1; [migrate to PostgreSQL](../../update/mysql_to_postgresql.md)
- required extension `pg_trgm`
- required extension `btree_gist`
- required extension `postgres_fdw` for Geo
- Errors like this in the `production/sidekiq` log; see: [Set default_transaction_isolation into read committed](https://docs.gitlab.com/omnibus/settings/database.html#set-default_transaction_isolation-into-read-committed):
......
......@@ -4150,6 +4150,11 @@ type EpicIssue implements Noteable {
"""
author: User!
"""
Indicates the issue is blocked
"""
blocked: Boolean!
"""
Timestamp of when the issue was closed
"""
......@@ -5767,6 +5772,11 @@ type Issue implements Noteable {
"""
author: User!
"""
Indicates the issue is blocked
"""
blocked: Boolean!
"""
Timestamp of when the issue was closed
"""
......@@ -6133,7 +6143,7 @@ input IssueSetConfidentialInput {
confidential: Boolean!
"""
The iid of the issue to mutate
The IID of the issue to mutate
"""
iid: String!
......@@ -6178,7 +6188,7 @@ input IssueSetDueDateInput {
dueDate: Time!
"""
The iid of the issue to mutate
The IID of the issue to mutate
"""
iid: String!
......@@ -6218,7 +6228,7 @@ input IssueSetIterationInput {
clientMutationId: String
"""
The iid of the issue to mutate
The IID of the issue to mutate
"""
iid: String!
......@@ -6263,7 +6273,7 @@ input IssueSetLockedInput {
clientMutationId: String
"""
The iid of the issue to mutate
The IID of the issue to mutate
"""
iid: String!
......@@ -6308,7 +6318,7 @@ input IssueSetWeightInput {
clientMutationId: String
"""
The iid of the issue to mutate
The IID of the issue to mutate
"""
iid: String!
......@@ -14027,7 +14037,7 @@ input UpdateIssueInput {
healthStatus: HealthStatus
"""
The iid of the issue to mutate
The IID of the issue to mutate
"""
iid: String!
......
......@@ -11590,6 +11590,24 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "blocked",
"description": "Indicates the issue is blocked",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "closedAt",
"description": "Timestamp of when the issue was closed",
......@@ -15862,6 +15880,24 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "blocked",
"description": "Indicates the issue is blocked",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "closedAt",
"description": "Timestamp of when the issue was closed",
......@@ -16936,7 +16972,7 @@
},
{
"name": "iid",
"description": "The iid of the issue to mutate",
"description": "The IID of the issue to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
......@@ -17066,7 +17102,7 @@
},
{
"name": "iid",
"description": "The iid of the issue to mutate",
"description": "The IID of the issue to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
......@@ -17196,7 +17232,7 @@
},
{
"name": "iid",
"description": "The iid of the issue to mutate",
"description": "The IID of the issue to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
......@@ -17322,7 +17358,7 @@
},
{
"name": "iid",
"description": "The iid of the issue to mutate",
"description": "The IID of the issue to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
......@@ -17452,7 +17488,7 @@
},
{
"name": "iid",
"description": "The iid of the issue to mutate",
"description": "The IID of the issue to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
......@@ -41369,7 +41405,7 @@
},
{
"name": "iid",
"description": "The iid of the issue to mutate",
"description": "The IID of the issue to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
......@@ -705,6 +705,7 @@ Relationship between an epic and an issue
| Name | Type | Description |
| --- | ---- | ---------- |
| `author` | User! | User that created the issue |
| `blocked` | Boolean! | Indicates the issue is blocked |
| `closedAt` | Time | Timestamp of when the issue was closed |
| `confidential` | Boolean! | Indicates the issue is confidential |
| `createdAt` | Time! | Timestamp of when the issue was created |
......@@ -864,6 +865,7 @@ Represents a Group Member
| Name | Type | Description |
| --- | ---- | ---------- |
| `author` | User! | User that created the issue |
| `blocked` | Boolean! | Indicates the issue is blocked |
| `closedAt` | Time | Timestamp of when the issue was closed |
| `confidential` | Boolean! | Indicates the issue is confidential |
| `createdAt` | Time! | Timestamp of when the issue was created |
......
......@@ -296,6 +296,74 @@ Namespaces should be PascalCase.
Note: The namespace should be removed from the translation. See the [translation
guidelines for more details](translation.md#namespaced-strings).
### HTML
We no longer include HTML directly in the strings that are submitted for translation. This is for a couple of reasons:
1. It introduces a chance for the translated string to accidentally include invalid HTML.
1. It introduces a security risk where translated strings become an attack vector for XSS, as noted by the
[Open Web Application Security Project (OWASP)](https://owasp.org/www-community/attacks/xss/).
To include formatting in the translated string, we can do the following:
- In Ruby/HAML:
```ruby
html_escape(_('Some %{strongOpen}bold%{strongClose} text.')) % { strongOpen: '<strong>'.html_safe, strongClose: '</strong>'.html_safe }
# => 'Some <strong>bold</strong> text.'
```
- In JavaScript:
```javascript
sprintf(__('Some %{strongOpen}bold%{strongClose} text.'), { strongOpen: '<strong>', strongClose: '</strong>'}, false);
// => 'Some <strong>bold</strong> text.'
```
- In Vue
See the section on [interpolation](#interpolation).
When [this translation helper issue](https://gitlab.com/gitlab-org/gitlab/-/issues/217935) is complete, we'll update the
process of including formatting in translated strings.
#### Including Angle Brackets
If a string contains angles brackets (`<`/`>`) that are not used for HTML, it will still be flagged by the
`rake gettext:lint` linter.
To avoid this error, use the applicable HTML entity code (`&lt;` or `&gt;`) instead:
- In Ruby/HAML:
```ruby
html_escape_once(_('In &lt; 1 hour')).html_safe
# => 'In < 1 hour'
```
- In JavaScript:
```javascript
import sanitize from 'sanitize-html';
const i18n = { LESS_THAN_ONE_HOUR: sanitize(__('In &lt; 1 hours'), { allowedTags: [] }) };
// ... using the string
element.innerHTML = i18n.LESS_THAN_ONE_HOUR;
// => 'In < 1 hour'
```
- In Vue:
```vue
<gl-sprintf :message="s__('In &lt; 1 hours')"/>
// => 'In < 1 hour'
```
### Dates / times
- In JavaScript:
......@@ -555,6 +623,7 @@ The linter will take the following into account:
- There should be no variables used in a translation that aren't in the
message ID
- Errors during translation.
- Presence of angle brackets (`<` or `>`)
The errors are grouped per file, and per message ID:
......
......@@ -25,14 +25,15 @@ suggesting to automate this process. Disapproving will exclude the
invalid translation, the merge request will be updated within a few
minutes.
If the translation has failed validation due to angle brackets `<` or `>`
it should be disapproved on CrowdIn as our strings should be
using [variables](externalization.md#html) for HTML instead.
It might be handy to pause the integration on the CrowdIn side for a
little while so translations don't keep coming. This can be done by
clicking `Pause sync` on the [CrowdIn integration settings
page](https://translate.gitlab.com/project/gitlab-ee/settings#integration).
When all failures are resolved, the translations need to be double
checked once more as discussed in [confidential issue](../../user/project/issues/confidential_issues.md) `https://gitlab.com/gitlab-org/gitlab/-/issues/19485`.
## Merging translations
When all translations are found good and pipelines pass the
......
......@@ -473,9 +473,9 @@ Since we're adding our SSL certificate at the load balancer, we do not need GitL
sudo gitlab-ctl reconfigure
```
#### Install the `pg_trgm` extension for PostgreSQL
#### Install the required extensions for PostgreSQL
From your GitLab instance, connect to the RDS instance to verify access and to install the required `pg_trgm` extension.
From your GitLab instance, connect to the RDS instance to verify access and to install the required `pg_trgm` and `btree_gist` extensions.
To find the host or endpoint, navigate to **Amazon RDS > Databases** and click on the database you created earlier. Look for the endpoint under the **Connectivity & security** tab.
......@@ -492,6 +492,7 @@ psql (10.9)
Type "help" for help.
gitlab=# CREATE EXTENSION pg_trgm;
gitlab=# CREATE EXTENSION btree_gist;
gitlab=# \q
```
......
......@@ -333,12 +333,18 @@ Starting from GitLab 12.1, only PostgreSQL is supported. Since GitLab 13.0, we r
sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
```
1. Create the `pg_trgm` extension (required for GitLab 8.6+):
1. Create the `pg_trgm` extension:
```shell
sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
```
1. Create the `btree_gist` extension (required for GitLab 13.1+):
```shell
sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS btree_gist;"
```
1. Create the GitLab production database and grant all privileges on the database:
```shell
......@@ -369,6 +375,24 @@ Starting from GitLab 12.1, only PostgreSQL is supported. Since GitLab 13.0, we r
(1 row)
```
1. Check if the `btree_gist` extension is enabled:
```sql
SELECT true AS enabled
FROM pg_available_extensions
WHERE name = 'btree_gist'
AND installed_version IS NOT NULL;
```
If the extension is enabled this will produce the following output:
```plaintext
enabled
---------
t
(1 row)
```
1. Quit the database session:
```shell
......
......@@ -140,8 +140,8 @@ GitLab version | Minimum PostgreSQL version
12.10 | 11
13.0 | 11
You must also ensure the `pg_trgm` extension is loaded into every
GitLab database. This extension [can be enabled](https://www.postgresql.org/docs/11/sql-createextension.html) using a PostgreSQL super user.
You must also ensure the `pg_trgm` and `btree_gist` extensions are loaded into every
GitLab database. These extensions [can be enabled](https://www.postgresql.org/docs/11/sql-createextension.html) using a PostgreSQL super user.
On some systems you may need to install an additional package (for example,
`postgresql-contrib`) for this extension to become available.
......
......@@ -233,6 +233,10 @@ or "blocked" messages when using the command line,
their accounts may have been un-confirmed.
In that case, please ask them to check their email for a re-confirmation link.
GitLab 13.2.0 relies on the `btree_gist` extension for PostgreSQL. For installations with an externally managed PostgreSQL setup, please make sure to
[install the extension manually](https://www.postgresql.org/docs/11/sql-createextension.html) before upgrading GitLab if the database user for GitLab
is not a superuser. This is not necessary for installations using a GitLab managed PostgreSQL database.
### 13.1.0
In 13.1.0, you must upgrade to either:
......
......@@ -35,7 +35,7 @@ Burndown Charts are generally used for tracking and analyzing the completion of
a milestone. Therefore, their use cases are tied to the
[use you are assigning your milestone to](index.md).
To exemplify, suppose you lead a team of developers in a large company,
For example, suppose you lead a team of developers in a large company,
and you follow this workflow:
- Your company set the goal for the quarter to deliver 10 new features for your app
......
......@@ -43,9 +43,7 @@ module Gitlab
end
def suite_errors
test_suites.each_with_object({}) do |(name, suite), errors|
errors[suite.name] = suite.suite_error if suite.suite_error
end
test_suites.transform_values(&:suite_error).compact
end
TestCase::STATUS_TYPES.each do |status_type|
......
此差异已折叠。
......@@ -5,13 +5,14 @@ module Gitlab
class PoLinter
include Gitlab::Utils::StrongMemoize
attr_reader :po_path, :translation_entries, :metadata_entry, :locale
attr_reader :po_path, :translation_entries, :metadata_entry, :locale, :html_todolist
VARIABLE_REGEX = /%{\w*}|%[a-z]/.freeze
def initialize(po_path, locale = I18n.locale.to_s)
def initialize(po_path:, html_todolist:, locale: I18n.locale.to_s)
@po_path = po_path
@locale = locale
@html_todolist = html_todolist
end
def errors
......@@ -19,7 +20,7 @@ module Gitlab
end
def validate_po
if parse_error = parse_po
if (parse_error = parse_po)
return 'PO-syntax errors' => [parse_error]
end
......@@ -38,7 +39,11 @@ module Gitlab
end
@translation_entries = entries.map do |entry_data|
Gitlab::I18n::TranslationEntry.new(entry_data, metadata_entry.expected_forms)
Gitlab::I18n::TranslationEntry.new(
entry_data: entry_data,
nplurals: metadata_entry.expected_forms,
html_allowed: html_todolist.fetch(entry_data[:msgid], false)
)
end
nil
......@@ -66,6 +71,7 @@ module Gitlab
validate_newlines(errors, entry)
validate_number_of_plurals(errors, entry)
validate_unescaped_chars(errors, entry)
validate_html(errors, entry)
validate_translation(errors, entry)
errors
......@@ -85,6 +91,23 @@ module Gitlab
end
end
def validate_html(errors, entry)
common_message = 'contains < or >. Use variables to include HTML in the string, or the &lt; and &gt; codes ' \
'for the symbols. For more info see: https://docs.gitlab.com/ee/development/i18n/externalization.html#html'
if entry.msgid_contains_potential_html? && !entry.msgid_html_allowed?
errors << common_message
end
if entry.plural_id_contains_potential_html? && !entry.plural_id_html_allowed?
errors << 'plural id ' + common_message
end
if entry.translations_contain_potential_html? && !entry.translations_html_allowed?
errors << 'translation ' + common_message
end
end
def validate_number_of_plurals(errors, entry)
return unless metadata_entry&.expected_forms
return unless entry.translated?
......
......@@ -4,12 +4,14 @@ module Gitlab
module I18n
class TranslationEntry
PERCENT_REGEX = /(?:^|[^%])%(?!{\w*}|[a-z%])/.freeze
ANGLE_BRACKET_REGEX = /[<>]/.freeze
attr_reader :nplurals, :entry_data
attr_reader :nplurals, :entry_data, :html_allowed
def initialize(entry_data, nplurals)
def initialize(entry_data:, nplurals:, html_allowed:)
@entry_data = entry_data
@nplurals = nplurals
@html_allowed = html_allowed
end
def msgid
......@@ -83,8 +85,38 @@ module Gitlab
string =~ PERCENT_REGEX
end
def msgid_contains_potential_html?
contains_angle_brackets?(msgid)
end
def plural_id_contains_potential_html?
contains_angle_brackets?(plural_id)
end
def translations_contain_potential_html?
all_translations.any? { |translation| contains_angle_brackets?(translation) }
end
def msgid_html_allowed?
html_allowed.present?
end
def plural_id_html_allowed?
html_allowed.present? && html_allowed['plural_id'] == plural_id
end
def translations_html_allowed?
html_allowed.present? && all_translations.all? do |translation|
html_allowed['translations'].include?(translation)
end
end
private
def contains_angle_brackets?(string)
string =~ ANGLE_BRACKET_REGEX
end
def translation_entries
@translation_entries ||= entry_data.fetch_values(*translation_keys)
.reject(&:empty?)
......
......@@ -12,6 +12,14 @@ namespace :gettext do
)
end
# Disallow HTML from translatable strings
# See: https://docs.gitlab.com/ee/development/i18n/externalization.html#html
def html_todolist
return @html_todolist if defined?(@html_todolist)
@html_todolist = YAML.load_file(Rails.root.join('lib/gitlab/i18n/html_todo.yml'))
end
task :compile do
# See: https://gitlab.com/gitlab-org/gitlab-foss/issues/33014#note_31218998
FileUtils.touch(File.join(Rails.root, 'locale/gitlab.pot'))
......@@ -54,11 +62,11 @@ namespace :gettext do
linters = files.map do |file|
locale = File.basename(File.dirname(file))
Gitlab::I18n::PoLinter.new(file, locale)
Gitlab::I18n::PoLinter.new(po_path: file, html_todolist: html_todolist, locale: locale)
end
pot_file = Rails.root.join('locale/gitlab.pot')
linters.unshift(Gitlab::I18n::PoLinter.new(pot_file))
linters.unshift(Gitlab::I18n::PoLinter.new(po_path: pot_file, html_todolist: html_todolist))
failed_linters = linters.select { |linter| linter.errors.any? }
......
......@@ -87,7 +87,7 @@ module QA
repository.init_repository
expect { repository.pull(repository_uri_ssh, branch_name) }
.to raise_error(QA::Git::Repository::RepositoryCommandError, /[fatal: Could not read from remote repository.]+/)
.to raise_error(QA::Git::Repository::RepositoryCommandError, /fatal: Could not read from remote repository\./)
end
end
......
......@@ -86,7 +86,7 @@ module QA
repository.init_repository
expect { repository.pull(repository_uri_ssh, branch_name) }
.to raise_error(QA::Git::Repository::RepositoryCommandError, /[fatal: Could not read from remote repository.]+/)
.to raise_error(QA::Git::Repository::RepositoryCommandError, /fatal: Could not read from remote repository\./)
end
end
......
# Spanish translations for gitlab package.
# Copyright (C) 2017 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the gitlab package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2017-07-13 12:10-0500\n"
"Language-Team: Spanish\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"Last-Translator: Translator <test@example.com>\n"
"X-Generator: Poedit 2.0.2\n"
msgid "String with some <strong>emphasis</strong>"
msgid_plural "String with lots of <strong>emphasis</strong>"
msgstr[0] "Translated string with some <strong>emphasis</strong>"
msgstr[1] "Translated string with lots of <strong>emphasis</strong>"
msgid "String with a legitimate < use"
msgid_plural "String with lots of < > uses"
msgstr[0] "Translated string with a legitimate < use"
msgstr[1] "Translated string with lots of < > uses"
......@@ -73,9 +73,6 @@ msgid_plural "Branches"
msgstr[0] "Rama"
msgstr[1] "Ramas"
msgid "Branch <strong>%{branch_name}</strong> was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}"
msgstr "La rama <strong>%{branch_name}</strong> fue creada. Para configurar el auto despliegue, escoge una plantilla Yaml para GitLab CI y envía tus cambios. %{link_to_autodeploy_doc}"
msgid "BranchSwitcherPlaceholder|Search branches"
msgstr "Buscar ramas"
......
......@@ -13,8 +13,6 @@ exports[`Design management index page designs does not render toolbar when there
<ol
class="list-unstyled row"
>
<!---->
<li
class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
data-testid="design-dropzone-wrapper"
......@@ -144,8 +142,6 @@ exports[`Design management index page designs renders designs list and header wi
<ol
class="list-unstyled row"
>
<!---->
<li
class="gl-flex-direction-column col-md-6 col-lg-3 gl-mb-3"
data-testid="design-dropzone-wrapper"
......@@ -292,12 +288,6 @@ exports[`Design management index page when has no designs renders design dropzon
<ol
class="list-unstyled row"
>
<span
class="gl-font-weight-bold gl-font-weight-bold gl-ml-5 gl-mb-4"
>
Designs
</span>
<li
class="col-12"
data-testid="design-dropzone-wrapper"
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe NamespaceStorageLimitAlertHelper do
describe '#display_namespace_storage_limit_alert!' do
it 'is defined in CE' do
expect { helper.display_namespace_storage_limit_alert! }.not_to raise_error
end
end
end
......@@ -6,7 +6,7 @@ require 'simple_po_parser'
# Disabling this cop to allow for multi-language examples in comments
# rubocop:disable Style/AsciiComments
RSpec.describe Gitlab::I18n::PoLinter do
let(:linter) { described_class.new(po_path) }
let(:linter) { described_class.new(po_path: po_path, html_todolist: {}) }
let(:po_path) { 'spec/fixtures/valid.po' }
def fake_translation(msgid:, translation:, plural_id: nil, plurals: [])
......@@ -23,8 +23,9 @@ RSpec.describe Gitlab::I18n::PoLinter do
end
Gitlab::I18n::TranslationEntry.new(
data,
plurals.size + 1
entry_data: data,
nplurals: plurals.size + 1,
html_allowed: nil
)
end
......@@ -145,6 +146,67 @@ RSpec.describe Gitlab::I18n::PoLinter do
expect(errors[message_id]).to include(expected_error)
end
end
context 'when an entry contains html' do
let(:po_path) { 'spec/fixtures/potential_html.po' }
it 'presents an error for each component containing angle brackets' do
message_id = 'String with some <strong>emphasis</strong>'
expect(errors[message_id]).to match_array [
a_string_starting_with('contains < or >.'),
a_string_starting_with('plural id contains < or >.'),
a_string_starting_with('translation contains < or >.')
]
end
end
context 'when an entry contains html on the todolist' do
subject(:linter) { described_class.new(po_path: po_path, html_todolist: todolist) }
let(:po_path) { 'spec/fixtures/potential_html.po' }
let(:todolist) do
{
'String with a legitimate < use' => {
'plural_id' => 'String with lots of < > uses',
'translations' => [
'Translated string with a legitimate < use',
'Translated string with lots of < > uses'
]
}
}
end
it 'does not present an error' do
message_id = 'String with a legitimate < use'
expect(errors[message_id]).to be_nil
end
end
context 'when an entry on the html todolist has changed' do
subject(:linter) { described_class.new(po_path: po_path, html_todolist: todolist) }
let(:po_path) { 'spec/fixtures/potential_html.po' }
let(:todolist) do
{
'String with a legitimate < use' => {
'plural_id' => 'String with lots of < > uses',
'translations' => [
'Translated string with a different legitimate < use',
'Translated string with lots of < > uses'
]
}
}
end
it 'presents an error for the changed component' do
message_id = 'String with a legitimate < use'
expect(errors[message_id])
.to include a_string_starting_with('translation contains < or >.')
end
end
end
describe '#parse_po' do
......@@ -200,6 +262,7 @@ RSpec.describe Gitlab::I18n::PoLinter do
expect(linter).to receive(:validate_number_of_plurals).with([], fake_entry)
expect(linter).to receive(:validate_unescaped_chars).with([], fake_entry)
expect(linter).to receive(:validate_translation).with([], fake_entry)
expect(linter).to receive(:validate_html).with([], fake_entry)
linter.validate_entry(fake_entry)
end
......@@ -212,8 +275,9 @@ RSpec.describe Gitlab::I18n::PoLinter do
allow(linter).to receive(:metadata_entry).and_return(fake_metadata)
fake_entry = Gitlab::I18n::TranslationEntry.new(
{ msgid: 'the singular', msgid_plural: 'the plural', 'msgstr[0]' => 'the singular' },
2
entry_data: { msgid: 'the singular', msgid_plural: 'the plural', 'msgstr[0]' => 'the singular' },
nplurals: 2,
html_allowed: nil
)
errors = []
......
......@@ -6,7 +6,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#singular_translation' do
it 'returns the normal `msgstr` for translations without plural' do
data = { msgid: 'Hello world', msgstr: 'Bonjour monde' }
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry.singular_translation).to eq('Bonjour monde')
end
......@@ -18,7 +18,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
'msgstr[0]' => 'Bonjour monde',
'msgstr[1]' => 'Bonjour mondes'
}
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry.singular_translation).to eq('Bonjour monde')
end
......@@ -27,7 +27,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#all_translations' do
it 'returns all translations for singular translations' do
data = { msgid: 'Hello world', msgstr: 'Bonjour monde' }
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry.all_translations).to eq(['Bonjour monde'])
end
......@@ -39,7 +39,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
'msgstr[0]' => 'Bonjour monde',
'msgstr[1]' => 'Bonjour mondes'
}
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry.all_translations).to eq(['Bonjour monde', 'Bonjour mondes'])
end
......@@ -52,7 +52,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
msgid_plural: 'Hello worlds',
'msgstr[0]' => 'Bonjour monde'
}
entry = described_class.new(data, 1)
entry = described_class.new(entry_data: data, nplurals: 1, html_allowed: nil)
expect(entry.plural_translations).to eq(['Bonjour monde'])
end
......@@ -65,7 +65,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
'msgstr[1]' => 'Bonjour mondes',
'msgstr[2]' => 'Bonjour tous les mondes'
}
entry = described_class.new(data, 3)
entry = described_class.new(entry_data: data, nplurals: 3, html_allowed: nil)
expect(entry.plural_translations).to eq(['Bonjour mondes', 'Bonjour tous les mondes'])
end
......@@ -77,7 +77,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
msgid: 'hello world',
msgstr: 'hello'
}
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry).to have_singular_translation
end
......@@ -89,7 +89,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
"msgstr[0]" => 'hello world',
"msgstr[1]" => 'hello worlds'
}
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry).to have_singular_translation
end
......@@ -100,7 +100,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
msgid_plural: 'hello worlds',
"msgstr[0]" => 'hello worlds'
}
entry = described_class.new(data, 1)
entry = described_class.new(entry_data: data, nplurals: 1, html_allowed: nil)
expect(entry).not_to have_singular_translation
end
......@@ -109,7 +109,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#msgid_contains_newlines' do
it 'is true when the msgid is an array' do
data = { msgid: %w(hello world) }
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry.msgid_has_multiple_lines?).to be_truthy
end
......@@ -118,7 +118,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#plural_id_contains_newlines' do
it 'is true when the msgid is an array' do
data = { msgid_plural: %w(hello world) }
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry.plural_id_has_multiple_lines?).to be_truthy
end
......@@ -127,7 +127,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#translations_contain_newlines' do
it 'is true when the msgid is an array' do
data = { msgstr: %w(hello world) }
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry.translations_have_multiple_lines?).to be_truthy
end
......@@ -135,7 +135,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#contains_unescaped_chars' do
let(:data) { { msgid: '' } }
let(:entry) { described_class.new(data, 2) }
let(:entry) { described_class.new(entry_data: data, nplurals: 2, html_allowed: nil) }
it 'is true when the msgid is an array' do
string = '「100%確定」'
......@@ -177,7 +177,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#msgid_contains_unescaped_chars' do
it 'is true when the msgid contains a `%`' do
data = { msgid: '「100%確定」' }
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry).to receive(:contains_unescaped_chars?).and_call_original
expect(entry.msgid_contains_unescaped_chars?).to be_truthy
......@@ -187,7 +187,7 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#plural_id_contains_unescaped_chars' do
it 'is true when the plural msgid contains a `%`' do
data = { msgid_plural: '「100%確定」' }
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry).to receive(:contains_unescaped_chars?).and_call_original
expect(entry.plural_id_contains_unescaped_chars?).to be_truthy
......@@ -197,10 +197,136 @@ RSpec.describe Gitlab::I18n::TranslationEntry do
describe '#translations_contain_unescaped_chars' do
it 'is true when the translation contains a `%`' do
data = { msgstr: '「100%確定」' }
entry = described_class.new(data, 2)
entry = described_class.new(entry_data: data, nplurals: 2, html_allowed: nil)
expect(entry).to receive(:contains_unescaped_chars?).and_call_original
expect(entry.translations_contain_unescaped_chars?).to be_truthy
end
end
describe '#msgid_contains_potential_html?' do
subject(:entry) { described_class.new(entry_data: data, nplurals: 2, html_allowed: nil) }
context 'when there are no angle brackets in the msgid' do
let(:data) { { msgid: 'String with no brackets' } }
it 'returns false' do
expect(entry.msgid_contains_potential_html?).to be_falsey
end
end
context 'when there are angle brackets in the msgid' do
let(:data) { { msgid: 'String with <strong> tag' } }
it 'returns true' do
expect(entry.msgid_contains_potential_html?).to be_truthy
end
end
end
describe '#plural_id_contains_potential_html?' do
subject(:entry) { described_class.new(entry_data: data, nplurals: 2, html_allowed: nil) }
context 'when there are no angle brackets in the plural_id' do
let(:data) { { msgid_plural: 'String with no brackets' } }
it 'returns false' do
expect(entry.plural_id_contains_potential_html?).to be_falsey
end
end
context 'when there are angle brackets in the plural_id' do
let(:data) { { msgid_plural: 'This string has a <strong>' } }
it 'returns true' do
expect(entry.plural_id_contains_potential_html?).to be_truthy
end
end
end
describe '#translations_contain_potential_html?' do
subject(:entry) { described_class.new(entry_data: data, nplurals: 2, html_allowed: nil) }
context 'when there are no angle brackets in the translations' do
let(:data) { { msgstr: 'This string has no angle brackets' } }
it 'returns false' do
expect(entry.translations_contain_potential_html?).to be_falsey
end
end
context 'when there are angle brackets in the translations' do
let(:data) { { msgstr: 'This string has a <strong>' } }
it 'returns true' do
expect(entry.translations_contain_potential_html?).to be_truthy
end
end
end
describe '#msgid_html_allowed?' do
subject(:entry) do
described_class.new(entry_data: { msgid: 'String with a <strong>' }, nplurals: 2, html_allowed: html_todo)
end
context 'when the html in the string is in the todolist' do
let(:html_todo) { { 'plural_id' => nil, 'translations' => [] } }
it 'returns true' do
expect(entry.msgid_html_allowed?).to be true
end
end
context 'when the html in the string is not in the todolist' do
let(:html_todo) { nil }
it 'returns false' do
expect(entry.msgid_html_allowed?).to be false
end
end
end
describe '#plural_id_html_allowed?' do
subject(:entry) do
described_class.new(entry_data: { msgid_plural: 'String with many <strong>' }, nplurals: 2, html_allowed: html_todo)
end
context 'when the html in the string is in the todolist' do
let(:html_todo) { { 'plural_id' => 'String with many <strong>', 'translations' => [] } }
it 'returns true' do
expect(entry.plural_id_html_allowed?).to be true
end
end
context 'when the html in the string is not in the todolist' do
let(:html_todo) { { 'plural_id' => 'String with some <strong>', 'translations' => [] } }
it 'returns false' do
expect(entry.plural_id_html_allowed?).to be false
end
end
end
describe '#translations_html_allowed?' do
subject(:entry) do
described_class.new(entry_data: { msgstr: 'String with a <strong>' }, nplurals: 2, html_allowed: html_todo)
end
context 'when the html in the string is in the todolist' do
let(:html_todo) { { 'plural_id' => nil, 'translations' => ['String with a <strong>'] } }
it 'returns true' do
expect(entry.translations_html_allowed?).to be true
end
end
context 'when the html in the string is not in the todolist' do
let(:html_todo) { { 'plural_id' => nil, 'translations' => ['String with a different <strong>'] } }
it 'returns false' do
expect(entry.translations_html_allowed?).to be false
end
end
end
end
......@@ -60,16 +60,14 @@ RSpec.describe Gitlab::JobWaiter do
described_class.notify(waiter.key, 'a')
described_class.notify(waiter.key, 'b')
result = nil
expect { Timeout.timeout(1) { result = waiter.wait(2) } }.not_to raise_error
expect { Timeout.timeout(1) { waiter.wait(2) } }.not_to raise_error
end
it 'increments job_waiter_started_total and job_waiter_timeouts_total when it times out' do
expect(started_total).to receive(:increment).with(worker: 'Foo')
expect(timeouts_total).to receive(:increment).with(worker: 'Foo')
result = nil
expect { Timeout.timeout(2) { result = waiter.wait(1) } }.not_to raise_error
expect { Timeout.timeout(2) { waiter.wait(1) } }.not_to raise_error
end
end
end
......
......@@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.153.0.tgz#79db0598382e6990d242f2e8dc0911903b1f558c"
integrity sha512-9letemutba300jT8BgxmYjUjMGDJifFFulPBNT4bxT+U2Ki+X+xs57Il3o/FNv5feJOPAlYS8Z/aEII8145g1g==
"@gitlab/ui@17.35.0":
version "17.35.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.35.0.tgz#dfbdae98dbc0e184c5d1bf0d1cd991f7aa915dcd"
integrity sha512-KvGOh5dPE+sCQKbvj4KlKtnmVOxP97K54Ibh9R6n4N37jAc71mlDInMXpNow1nf+7mAL6ca1cPknIkmuf2+B3g==
"@gitlab/ui@17.35.1":
version "17.35.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.35.1.tgz#bb35fc46e411a0d7abcf2b414c4932e45777ecaf"
integrity sha512-9GG3dtd5Io1r/BIuAvo1+HRCUJRkht3xk+r5OnpvNXAiwa6/DWD6bZcT9J6tcTRCRpDZBiOoe6P8ObfFLshg0Q==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册