Commit b5ad0617 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 0a6ffb54
...@@ -452,7 +452,7 @@ group :ed25519 do ...@@ -452,7 +452,7 @@ group :ed25519 do
end end
# Gitaly GRPC protocol definitions # Gitaly GRPC protocol definitions
gem 'gitaly', '~> 1.70.0' gem 'gitaly', '~> 1.73.0'
gem 'grpc', '~> 1.24.0' gem 'grpc', '~> 1.24.0'
......
...@@ -359,7 +359,7 @@ GEM ...@@ -359,7 +359,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
git (1.5.0) git (1.5.0)
gitaly (1.70.0) gitaly (1.73.0)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab-labkit (0.7.0) gitlab-labkit (0.7.0)
...@@ -1193,7 +1193,7 @@ DEPENDENCIES ...@@ -1193,7 +1193,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly (~> 1.70.0) gitaly (~> 1.73.0)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-labkit (~> 0.5) gitlab-labkit (~> 0.5)
gitlab-license (~> 1.0) gitlab-license (~> 1.0)
......
...@@ -59,6 +59,9 @@ export default { ...@@ -59,6 +59,9 @@ export default {
collapsedButtonIcon() { collapsedButtonIcon() {
return this.isTodo ? 'todo-done' : 'todo-add'; return this.isTodo ? 'todo-done' : 'todo-add';
}, },
collapsedButtonIconVisible() {
return this.collapsed && !this.isActionActive;
},
}, },
methods: { methods: {
handleButtonClick() { handleButtonClick() {
...@@ -82,8 +85,12 @@ export default { ...@@ -82,8 +85,12 @@ export default {
data-boundary="viewport" data-boundary="viewport"
@click="handleButtonClick" @click="handleButtonClick"
> >
<icon v-show="collapsed" :class="collapsedButtonIconClasses" :name="collapsedButtonIcon" /> <icon
<span v-show="!collapsed" class="issuable-todo-inner"> {{ buttonLabel }} </span> v-show="collapsedButtonIconVisible"
:class="collapsedButtonIconClasses"
:name="collapsedButtonIcon"
/>
<span v-show="!collapsed" class="issuable-todo-inner">{{ buttonLabel }}</span>
<gl-loading-icon v-show="isActionActive" :inline="true" /> <gl-loading-icon v-show="isActionActive" :inline="true" />
</button> </button>
</template> </template>
...@@ -61,10 +61,6 @@ ...@@ -61,10 +61,6 @@
padding-right: 0; padding-right: 0;
z-index: 300; z-index: 300;
.btn-sidebar-action {
display: inline-flex;
}
@include media-breakpoint-only(sm) { @include media-breakpoint-only(sm) {
&:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
padding-right: $gutter-collapsed-width; padding-right: $gutter-collapsed-width;
......
...@@ -16,6 +16,7 @@ class ProjectAutoDevops < ApplicationRecord ...@@ -16,6 +16,7 @@ class ProjectAutoDevops < ApplicationRecord
def predefined_variables def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables| Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'AUTO_DEVOPS_EXPLICITLY_ENABLED', value: '1') if enabled?
variables.concat(deployment_strategy_default_variables) variables.concat(deployment_strategy_default_variables)
end end
end end
......
...@@ -85,12 +85,36 @@ module Git ...@@ -85,12 +85,36 @@ module Git
before: oldrev, before: oldrev,
after: newrev, after: newrev,
ref: ref, ref: ref,
variables_attributes: generate_vars_from_push_options || [],
push_options: params[:push_options] || {}, push_options: params[:push_options] || {},
checkout_sha: Gitlab::DataBuilder::Push.checkout_sha( checkout_sha: Gitlab::DataBuilder::Push.checkout_sha(
project.repository, newrev, ref) project.repository, newrev, ref)
} }
end end
def ci_variables_from_push_options
strong_memoize(:ci_variables_from_push_options) do
params[:push_options]&.deep_symbolize_keys&.dig(:ci, :variable)
end
end
def generate_vars_from_push_options
return [] unless ci_variables_from_push_options
ci_variables_from_push_options.map do |var_definition, _count|
key, value = var_definition.to_s.split("=", 2)
# Accept only valid format. We ignore the following formats
# 1. "=123". In this case, `key` will be an empty string
# 2. "FOO". In this case, `value` will be nil.
# However, the format "FOO=" will result in key beign `FOO` and value
# being an empty string. This is acceptable.
next if key.blank? || value.nil?
{ "key" => key, "variable_type" => "env_var", "secret_value" => value }
end.compact
end
def push_data_params(commits:, with_changed_files: true) def push_data_params(commits:, with_changed_files: true)
{ {
oldrev: oldrev, oldrev: oldrev,
......
---
title: Vertically align collapse button on epic sidebar
merge_request: 19656
author:
type: fixed
---
title: Add CI variable to show when Auto-DevOps is explicitly enabled
merge_request: 20332
author:
type: changed
---
title: Support passing CI variables via git push options
merge_request: 20255
author:
type: added
...@@ -87,7 +87,7 @@ The following documentation relates to the DevOps **Manage** stage: ...@@ -87,7 +87,7 @@ The following documentation relates to the DevOps **Manage** stage:
| Manage Topics | Description | | Manage Topics | Description |
|:--------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |:--------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Authentication and<br/>Authorization](administration/auth/README.md) **(CORE ONLY)** | Supported authentication and authorization providers. | | [Authentication and<br/>Authorization](administration/auth/README.md) **(CORE ONLY)** | Supported authentication and authorization providers. |
| [GitLab Cycle Analytics](user/project/cycle_analytics.md) | Measure the time it takes to go from an [idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have. | | [GitLab Cycle Analytics](user/project/cycle_analytics.md) | Measure the time it takes to go from an [idea to production](https://about.gitlab.com/blog/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have. |
| [Instance Statistics](user/instance_statistics/index.md) | Discover statistics on how many GitLab features you use and user activity. | | [Instance Statistics](user/instance_statistics/index.md) | Discover statistics on how many GitLab features you use and user activity. |
<div align="right"> <div align="right">
...@@ -376,7 +376,7 @@ We have the following documentation to rapidly uplift your GitLab knowledge: ...@@ -376,7 +376,7 @@ We have the following documentation to rapidly uplift your GitLab knowledge:
| Topic | Description | | Topic | Description |
|:-----------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------| |:-----------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------|
| [GitLab basics guides](gitlab-basics/README.md) | Start working on the command line and with GitLab. | | [GitLab basics guides](gitlab-basics/README.md) | Start working on the command line and with GitLab. |
| [GitLab workflow overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/) | Enhance your workflow with the best of GitLab Workflow. | | [GitLab workflow overview](https://about.gitlab.com/blog/2016/10/25/gitlab-workflow-an-overview/) | Enhance your workflow with the best of GitLab Workflow. |
| [Get started with GitLab CI/CD](ci/quick_start/README.md) | Quickly implement GitLab CI/CD. | | [Get started with GitLab CI/CD](ci/quick_start/README.md) | Quickly implement GitLab CI/CD. |
| [Auto DevOps](topics/autodevops/index.md) | Learn more about GitLab's Auto DevOps. | | [Auto DevOps](topics/autodevops/index.md) | Learn more about GitLab's Auto DevOps. |
| [GitLab Markdown](user/markdown.md) | GitLab's advanced formatting system (GitLab Flavored Markdown) | | [GitLab Markdown](user/markdown.md) | GitLab's advanced formatting system (GitLab Flavored Markdown) |
......
...@@ -248,3 +248,16 @@ request: ...@@ -248,3 +248,16 @@ request:
1. The [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). 1. The [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
1. The [CI environment preparation](https://gitlab.com/gitlab-org/gitlab/blob/master/scripts/prepare_build.sh). 1. The [CI environment preparation](https://gitlab.com/gitlab-org/gitlab/blob/master/scripts/prepare_build.sh).
1. The [Omnibus package creator](https://gitlab.com/gitlab-org/omnibus-gitlab). 1. The [Omnibus package creator](https://gitlab.com/gitlab-org/omnibus-gitlab).
### Incremental improvements
We allow engineering time to fix small problems (with or without an
issue) that are incremental improvements, such as:
1. Unprioritized bug fixes (e.g. [Banner alerting of project move is
showing up everywhere](https://gitlab.com/gitlab-org/gitlab/merge_requests/18985))
1. Documentation improvements
1. Rubocop or Code Quality improvements
Tag a merge request with ~"Stuff that should Just Work" to track work in
this area.
...@@ -16,5 +16,5 @@ useful compilation of accessibility-related material. ...@@ -16,5 +16,5 @@ useful compilation of accessibility-related material.
[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules [audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules
[axe-website]: https://www.deque.com/axe/ [axe-website]: https://www.deque.com/axe/
[axe-firefox-extension]: https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/ [axe-firefox-extension]: https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/
[axe-chrome-extension]: https://chrome.google.com/webstore/detail/axe/lhdoppojpmngadmnindnejefpokejbdd [axe-chrome-extension]: https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd
[awesome-a11y]: https://github.com/brunopulis/awesome-a11y [awesome-a11y]: https://github.com/brunopulis/awesome-a11y
...@@ -28,7 +28,7 @@ New utility classes should be added to [`utilities.scss`](https://gitlab.com/git ...@@ -28,7 +28,7 @@ New utility classes should be added to [`utilities.scss`](https://gitlab.com/git
- `{variant}` is one of 'primary', 'secondary', 'success', 'warning', 'error' - `{variant}` is one of 'primary', 'secondary', 'success', 'warning', 'error'
- `{shade}` is one of the shades listed on [colors](https://design.gitlab.com/product-foundations/colors/) - `{shade}` is one of the shades listed on [colors](https://design.gitlab.com/product-foundations/colors/)
- `{size}` is a number from 1-6 from our [Type scale](https://design.gitlab.com/product-foundations/typography) - `{size}` is a number from 1-6 from our [Type scale](https://design.gitlab.com/product-foundations/typography/)
#### When should I create component classes? #### When should I create component classes?
......
...@@ -155,7 +155,7 @@ refresh_service.execute(oldrev, newrev, ref) ...@@ -155,7 +155,7 @@ refresh_service.execute(oldrev, newrev, ref)
See ["Why is it bad style to `rescue Exception => e` in Ruby?"](https://stackoverflow.com/questions/10048173/why-is-it-bad-style-to-rescue-exception-e-in-ruby). See ["Why is it bad style to `rescue Exception => e` in Ruby?"](https://stackoverflow.com/questions/10048173/why-is-it-bad-style-to-rescue-exception-e-in-ruby).
_**Note:** This rule is [enforced automatically by _**Note:** This rule is [enforced automatically by
Rubocop](https://gitlab.com/gitlab-org/gitlab/blob/8-4-stable/.rubocop.yml#L911-914)._ Rubocop](https://gitlab.com/gitlab-org/gitlab-foss/blob/8-4-stable/.rubocop.yml#L911-914)._
## Do not use inline JavaScript in views ## Do not use inline JavaScript in views
......
# Issuable-like Rails models utilities # Issuable-like Rails models utilities
GitLab Rails codebase contains several models that hold common functionality and behave similarly to an [Issue]. Other GitLab Rails codebase contains several models that hold common functionality and behave similarly to
examples of `Issuable`s are [Merge Requests] and [Epics]. [Issues](https://docs.gitlab.com/ee/user/project/issues/). Other examples of "issuables"
are [Merge Requests](https://docs.gitlab.com/ee/user/project/merge_requests/) and
[Epics](https://docs.gitlab.com/ee/user/group/epics/).
This guide accumulates guidelines on working with such Rails models. This guide accumulates guidelines on working with such Rails models.
...@@ -13,7 +15,3 @@ There are max length constraints for the most important text fields for `Issuabl ...@@ -13,7 +15,3 @@ There are max length constraints for the most important text fields for `Issuabl
- `title_html`: 800 chars - `title_html`: 800 chars
- `description`: 1 megabyte - `description`: 1 megabyte
- `description_html`: 5 megabytes - `description_html`: 5 megabytes
[Issue]: https://docs.gitlab.com/ee/user/project/issues
[Merge Requests]: https://docs.gitlab.com/ee/user/project/merge_requests
[Epics]: https://docs.gitlab.com/ee/user/group/epics
...@@ -65,7 +65,6 @@ Libraries with the following licenses require legal approval for use: ...@@ -65,7 +65,6 @@ Libraries with the following licenses require legal approval for use:
- [GNU GPL](https://choosealicense.com/licenses/gpl-3.0/) (version 1, [version 2][GPLv2], [version 3][GPLv3], or any future versions): GPL-licensed libraries cannot be linked to from non-GPL projects. - [GNU GPL](https://choosealicense.com/licenses/gpl-3.0/) (version 1, [version 2][GPLv2], [version 3][GPLv3], or any future versions): GPL-licensed libraries cannot be linked to from non-GPL projects.
- [GNU AGPLv3](https://choosealicense.com/licenses/agpl-3.0/): AGPL-licensed libraries cannot be linked to from non-GPL projects. - [GNU AGPLv3](https://choosealicense.com/licenses/agpl-3.0/): AGPL-licensed libraries cannot be linked to from non-GPL projects.
- [Open Software License (OSL)][OSL]: is a copyleft license. In addition, the FSF [recommend against its use][OSL-GNU]. - [Open Software License (OSL)][OSL]: is a copyleft license. In addition, the FSF [recommend against its use][OSL-GNU].
- [Facebook BSD + PATENTS][Facebook]: is a 3-clause BSD license with a patent grant that has been deemed [Category X][x-list] by the Apache foundation.
- [WTFPL][WTFPL]: is a public domain dedication [rejected by the OSI (3.2)][WTFPL-OSI]. Also has a strong language which is not in accordance with our diversity policy. - [WTFPL][WTFPL]: is a public domain dedication [rejected by the OSI (3.2)][WTFPL-OSI]. Also has a strong language which is not in accordance with our diversity policy.
## GPL Cooperation Commitment ## GPL Cooperation Commitment
...@@ -124,7 +123,6 @@ Dependencies which are only used in development or test environment are exempt f ...@@ -124,7 +123,6 @@ Dependencies which are only used in development or test environment are exempt f
[Org-Repo]: https://gitlab.com/gitlab-com/organization [Org-Repo]: https://gitlab.com/gitlab-com/organization
[UNLICENSE]: https://unlicense.org [UNLICENSE]: https://unlicense.org
[OWFa1]: http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0 [OWFa1]: http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0
[Facebook]: https://code.facebook.com/pages/850928938376556
[x-list]: https://www.apache.org/legal/resolved.html#category-x [x-list]: https://www.apache.org/legal/resolved.html#category-x
[Acceptable-Licenses]: #acceptable-licenses [Acceptable-Licenses]: #acceptable-licenses
[Unacceptable-Licenses]: #unacceptable-licenses [Unacceptable-Licenses]: #unacceptable-licenses
......
...@@ -39,7 +39,7 @@ Note that currently on GitLab.com, any messages in `production.log` will ...@@ -39,7 +39,7 @@ Note that currently on GitLab.com, any messages in `production.log` will
NOT get indexed by Elasticsearch due to the sheer volume and noise. They NOT get indexed by Elasticsearch due to the sheer volume and noise. They
do end up in Google Stackdriver, but it is still harder to search for do end up in Google Stackdriver, but it is still harder to search for
logs there. See the [GitLab.com logging logs there. See the [GitLab.com logging
documentation](https://gitlab.com/gitlab-com/runbooks/blob/master/howto/logging.md) documentation](https://gitlab.com/gitlab-com/runbooks/blob/master/logging/doc/README.md)
for more details. for more details.
## Use structured (JSON) logging ## Use structured (JSON) logging
......
...@@ -104,9 +104,9 @@ end ...@@ -104,9 +104,9 @@ end
### Example database migration test ### Example database migration test
This spec tests the This spec tests the
[`db/post_migrate/20170526185842_migrate_pipeline_stages.rb`](https://gitlab.com/gitlab-org/gitlab/blob/v11.6.5/db/post_migrate/20170526185842_migrate_pipeline_stages.rb) [`db/post_migrate/20170526185842_migrate_pipeline_stages.rb`](https://gitlab.com/gitlab-org/gitlab-foss/blob/v11.6.5/db/post_migrate/20170526185842_migrate_pipeline_stages.rb)
migration. You can find the complete spec in migration. You can find the complete spec in
[`spec/migrations/migrate_pipeline_stages_spec.rb`](https://gitlab.com/gitlab-org/gitlab/blob/v11.6.5/spec/migrations/migrate_pipeline_stages_spec.rb). [`spec/migrations/migrate_pipeline_stages_spec.rb`](https://gitlab.com/gitlab-org/gitlab-foss/blob/v11.6.5/spec/migrations/migrate_pipeline_stages_spec.rb).
```ruby ```ruby
require 'spec_helper' require 'spec_helper'
...@@ -171,9 +171,9 @@ end ...@@ -171,9 +171,9 @@ end
### Example background migration test ### Example background migration test
This spec tests the This spec tests the
[`lib/gitlab/background_migration/archive_legacy_traces.rb`](https://gitlab.com/gitlab-org/gitlab/blob/v11.6.5/lib/gitlab/background_migration/archive_legacy_traces.rb) [`lib/gitlab/background_migration/archive_legacy_traces.rb`](https://gitlab.com/gitlab-org/gitlab-foss/blob/v11.6.5/lib/gitlab/background_migration/archive_legacy_traces.rb)
background migration. You can find the complete spec on background migration. You can find the complete spec on
[`spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb`](https://gitlab.com/gitlab-org/gitlab/blob/v11.6.5/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb) [`spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb`](https://gitlab.com/gitlab-org/gitlab-foss/blob/v11.6.5/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb)
```ruby ```ruby
require 'spec_helper' require 'spec_helper'
......
...@@ -196,8 +196,8 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres ...@@ -196,8 +196,8 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
## 4. External Articles ## 4. External Articles
1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460) 1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460)
1. [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/) 1. [2014 Blog post by Chris Dixon - Software eats software development](https://cdixon.org/2014/04/13/software-eats-software-development)
1. [2015 Venture Beat article - Actually, Open Source is Eating the World](http://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/) 1. [2015 Venture Beat article - Actually, Open Source is Eating the World](https://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/)
## 5. Resources for GitLab Team Members ## 5. Resources for GitLab Team Members
......
...@@ -68,8 +68,8 @@ If you are expanding from a few projects to a larger number of projects within t ...@@ -68,8 +68,8 @@ If you are expanding from a few projects to a larger number of projects within t
From the project milestone list page, you can promote a project milestone to a group milestone. This will merge all project milestones across all projects in this group with the same name into a single group milestones. All issues and merge requests that previously were assigned one of these project milestones will now be assigned the new group milestones. This action cannot be reversed and the changes are permanent. From the project milestone list page, you can promote a project milestone to a group milestone. This will merge all project milestones across all projects in this group with the same name into a single group milestones. All issues and merge requests that previously were assigned one of these project milestones will now be assigned the new group milestones. This action cannot be reversed and the changes are permanent.
>**Note:** CAUTION: **Caution:**
Not all features on the project milestone view are available on the group milestone view. If you promote a project milestone to a group milestone, you will lose these features. See [Milestone view](#milestone-view) to see which features are missing from the group milestone view. From GitLab 12.4 and earlier, some information is lost when you promote a project milestone to a group milestone. Not all features on the project milestone view are available on the group milestone view. If you promote a project milestone to a group milestone, you will lose these features. See [Milestone view](#milestone-view) to see which features are missing from the group milestone view.
![Promote milestone](img/milestones_promote_milestone.png) ![Promote milestone](img/milestones_promote_milestone.png)
......
...@@ -16,11 +16,12 @@ module Gitlab ...@@ -16,11 +16,12 @@ module Gitlab
] ]
}, },
ci: { ci: {
keys: [:skip] keys: [:skip, :variable]
} }
}).freeze }).freeze
MULTI_VALUE_OPTIONS = [ MULTI_VALUE_OPTIONS = [
%w[ci variable],
%w[merge_request label], %w[merge_request label],
%w[merge_request unlabel] %w[merge_request unlabel]
].freeze ].freeze
......
...@@ -5758,9 +5758,6 @@ msgstr "" ...@@ -5758,9 +5758,6 @@ msgstr ""
msgid "DesignManagement|Adding a design with the same filename replaces the file in a new version." msgid "DesignManagement|Adding a design with the same filename replaces the file in a new version."
msgstr "" msgstr ""
msgid "DesignManagement|An error occurred while loading designs. Please try again."
msgstr ""
msgid "DesignManagement|Are you sure you want to delete the selected designs?" msgid "DesignManagement|Are you sure you want to delete the selected designs?"
msgstr "" msgstr ""
......
...@@ -51,6 +51,7 @@ describe('Issue card component', () => { ...@@ -51,6 +51,7 @@ describe('Issue card component', () => {
}, },
store, store,
sync: false, sync: false,
attachToDocument: true,
}); });
}); });
......
...@@ -7,11 +7,11 @@ import { reposServerResponse, parsedReposServerResponse } from '../mock_data'; ...@@ -7,11 +7,11 @@ import { reposServerResponse, parsedReposServerResponse } from '../mock_data';
describe('Registry List', () => { describe('Registry List', () => {
let wrapper; let wrapper;
const findCollapsibleContainer = w => w.findAll({ name: 'CollapsibeContainerRegisty' }); const findCollapsibleContainer = () => wrapper.findAll({ name: 'CollapsibeContainerRegisty' });
const findProjectEmptyState = w => w.find({ name: 'ProjectEmptyState' }); const findProjectEmptyState = () => wrapper.find({ name: 'ProjectEmptyState' });
const findGroupEmptyState = w => w.find({ name: 'GroupEmptyState' }); const findGroupEmptyState = () => wrapper.find({ name: 'GroupEmptyState' });
const findSpinner = w => w.find('.gl-spinner'); const findSpinner = () => wrapper.find('.gl-spinner');
const findCharacterErrorText = w => w.find('.js-character-error-text'); const findCharacterErrorText = () => wrapper.find('.js-character-error-text');
const propsData = { const propsData = {
endpoint: `${TEST_HOST}/foo`, endpoint: `${TEST_HOST}/foo`,
...@@ -59,16 +59,15 @@ describe('Registry List', () => { ...@@ -59,16 +59,15 @@ describe('Registry List', () => {
describe('with data', () => { describe('with data', () => {
it('should render a list of CollapsibeContainerRegisty', () => { it('should render a list of CollapsibeContainerRegisty', () => {
const containers = findCollapsibleContainer(wrapper); const containers = findCollapsibleContainer();
expect(wrapper.vm.repos.length).toEqual(reposServerResponse.length); expect(wrapper.vm.repos.length).toEqual(reposServerResponse.length);
expect(containers.length).toEqual(reposServerResponse.length); expect(containers.length).toEqual(reposServerResponse.length);
}); });
}); });
describe('without data', () => { describe('without data', () => {
let localWrapper;
beforeEach(() => { beforeEach(() => {
localWrapper = mount(registry, { wrapper = mount(registry, {
attachToDocument: true, attachToDocument: true,
sync: false, sync: false,
propsData, propsData,
...@@ -82,16 +81,14 @@ describe('Registry List', () => { ...@@ -82,16 +81,14 @@ describe('Registry List', () => {
}); });
it('should render project empty message', () => { it('should render project empty message', () => {
const projectEmptyState = findProjectEmptyState(localWrapper); const projectEmptyState = findProjectEmptyState();
expect(projectEmptyState.exists()).toBe(true); expect(projectEmptyState.exists()).toBe(true);
}); });
}); });
describe('while loading data', () => { describe('while loading data', () => {
let localWrapper;
beforeEach(() => { beforeEach(() => {
localWrapper = mount(registry, { wrapper = mount(registry, {
propsData, propsData,
computed: { computed: {
repos() { repos() {
...@@ -106,16 +103,14 @@ describe('Registry List', () => { ...@@ -106,16 +103,14 @@ describe('Registry List', () => {
}); });
it('should render a loading spinner', () => { it('should render a loading spinner', () => {
const spinner = findSpinner(localWrapper); const spinner = findSpinner();
expect(spinner.exists()).toBe(true); expect(spinner.exists()).toBe(true);
}); });
}); });
describe('invalid characters in path', () => { describe('invalid characters in path', () => {
let localWrapper;
beforeEach(() => { beforeEach(() => {
localWrapper = mount(registry, { wrapper = mount(registry, {
propsData: { propsData: {
...propsData, ...propsData,
characterError: true, characterError: true,
...@@ -130,7 +125,7 @@ describe('Registry List', () => { ...@@ -130,7 +125,7 @@ describe('Registry List', () => {
}); });
it('should render invalid characters error message', () => { it('should render invalid characters error message', () => {
const characterErrorText = findCharacterErrorText(localWrapper); const characterErrorText = findCharacterErrorText();
expect(characterErrorText.text()).toEqual( expect(characterErrorText.text()).toEqual(
'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More Information', 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More Information',
); );
......
...@@ -17,10 +17,10 @@ describe('collapsible registry container', () => { ...@@ -17,10 +17,10 @@ describe('collapsible registry container', () => {
let wrapper; let wrapper;
let store; let store;
const findDeleteBtn = (w = wrapper) => w.find('.js-remove-repo'); const findDeleteBtn = () => wrapper.find('.js-remove-repo');
const findContainerImageTags = (w = wrapper) => w.find('.container-image-tags'); const findContainerImageTags = () => wrapper.find('.container-image-tags');
const findToggleRepos = (w = wrapper) => w.findAll('.js-toggle-repo'); const findToggleRepos = () => wrapper.findAll('.js-toggle-repo');
const findDeleteModal = (w = wrapper) => w.find({ ref: 'deleteModal' }); const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' });
const mountWithStore = config => const mountWithStore = config =>
mount(collapsibleComponent, { mount(collapsibleComponent, {
...@@ -62,7 +62,7 @@ describe('collapsible registry container', () => { ...@@ -62,7 +62,7 @@ describe('collapsible registry container', () => {
}); });
const expectIsClosed = () => { const expectIsClosed = () => {
const container = findContainerImageTags(wrapper); const container = findContainerImageTags();
expect(container.exists()).toBe(false); expect(container.exists()).toBe(false);
expect(wrapper.vm.iconName).toEqual('angle-right'); expect(wrapper.vm.iconName).toEqual('angle-right');
}; };
...@@ -70,18 +70,20 @@ describe('collapsible registry container', () => { ...@@ -70,18 +70,20 @@ describe('collapsible registry container', () => {
it('should be closed by default', () => { it('should be closed by default', () => {
expectIsClosed(); expectIsClosed();
}); });
it('should be open when user clicks on closed repo', done => { it('should be open when user clicks on closed repo', done => {
const toggleRepos = findToggleRepos(wrapper); const toggleRepos = findToggleRepos();
toggleRepos.at(0).trigger('click'); toggleRepos.at(0).trigger('click');
Vue.nextTick(() => { Vue.nextTick(() => {
const container = findContainerImageTags(wrapper); const container = findContainerImageTags();
expect(container.exists()).toBe(true); expect(container.exists()).toBe(true);
expect(wrapper.vm.fetchList).toHaveBeenCalled(); expect(wrapper.vm.fetchList).toHaveBeenCalled();
done(); done();
}); });
}); });
it('should be closed when the user clicks on an opened repo', done => { it('should be closed when the user clicks on an opened repo', done => {
const toggleRepos = findToggleRepos(wrapper); const toggleRepos = findToggleRepos();
toggleRepos.at(0).trigger('click'); toggleRepos.at(0).trigger('click');
Vue.nextTick(() => { Vue.nextTick(() => {
toggleRepos.at(0).trigger('click'); toggleRepos.at(0).trigger('click');
...@@ -95,7 +97,7 @@ describe('collapsible registry container', () => { ...@@ -95,7 +97,7 @@ describe('collapsible registry container', () => {
describe('delete repo', () => { describe('delete repo', () => {
it('should be possible to delete a repo', () => { it('should be possible to delete a repo', () => {
const deleteBtn = findDeleteBtn(wrapper); const deleteBtn = findDeleteBtn();
expect(deleteBtn.exists()).toBe(true); expect(deleteBtn.exists()).toBe(true);
}); });
...@@ -132,7 +134,7 @@ describe('collapsible registry container', () => { ...@@ -132,7 +134,7 @@ describe('collapsible registry container', () => {
}); });
it('should not render delete button', () => { it('should not render delete button', () => {
const deleteBtn = findDeleteBtn(wrapper); const deleteBtn = findDeleteBtn();
expect(deleteBtn.exists()).toBe(false); expect(deleteBtn.exists()).toBe(false);
}); });
}); });
......
...@@ -19,13 +19,13 @@ describe('table registry', () => { ...@@ -19,13 +19,13 @@ describe('table registry', () => {
let wrapper; let wrapper;
let store; let store;
const findSelectAllCheckbox = (w = wrapper) => w.find('.js-select-all-checkbox > input'); const findSelectAllCheckbox = () => wrapper.find('.js-select-all-checkbox > input');
const findSelectCheckboxes = (w = wrapper) => w.findAll('.js-select-checkbox > input'); const findSelectCheckboxes = () => wrapper.findAll('.js-select-checkbox > input');
const findDeleteButton = (w = wrapper) => w.find({ ref: 'bulkDeleteButton' }); const findDeleteButton = () => wrapper.find({ ref: 'bulkDeleteButton' });
const findDeleteButtonsRow = (w = wrapper) => w.findAll('.js-delete-registry-row'); const findDeleteButtonsRow = () => wrapper.findAll('.js-delete-registry-row');
const findPagination = (w = wrapper) => w.find('.js-registry-pagination'); const findPagination = () => wrapper.find('.js-registry-pagination');
const findDeleteModal = (w = wrapper) => w.find({ ref: 'deleteModal' }); const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' });
const findImageId = (w = wrapper) => w.find({ ref: 'imageId' }); const findImageId = () => wrapper.find({ ref: 'imageId' });
const bulkDeletePath = 'path'; const bulkDeletePath = 'path';
const mountWithStore = config => const mountWithStore = config =>
...@@ -83,8 +83,8 @@ describe('table registry', () => { ...@@ -83,8 +83,8 @@ describe('table registry', () => {
describe('multi select', () => { describe('multi select', () => {
it('selecting a row should enable delete button', done => { it('selecting a row should enable delete button', done => {
const deleteBtn = findDeleteButton(wrapper); const deleteBtn = findDeleteButton();
const checkboxes = findSelectCheckboxes(wrapper); const checkboxes = findSelectCheckboxes();
expect(deleteBtn.attributes('disabled')).toBe('disabled'); expect(deleteBtn.attributes('disabled')).toBe('disabled');
...@@ -96,8 +96,8 @@ describe('table registry', () => { ...@@ -96,8 +96,8 @@ describe('table registry', () => {
}); });
it('selecting all checkbox should select all rows and enable delete button', done => { it('selecting all checkbox should select all rows and enable delete button', done => {
const selectAll = findSelectAllCheckbox(wrapper); const selectAll = findSelectAllCheckbox();
const checkboxes = findSelectCheckboxes(wrapper); const checkboxes = findSelectCheckboxes();
selectAll.trigger('click'); selectAll.trigger('click');
Vue.nextTick(() => { Vue.nextTick(() => {
...@@ -108,8 +108,8 @@ describe('table registry', () => { ...@@ -108,8 +108,8 @@ describe('table registry', () => {
}); });
it('deselecting select all checkbox should deselect all rows and disable delete button', done => { it('deselecting select all checkbox should deselect all rows and disable delete button', done => {
const checkboxes = findSelectCheckboxes(wrapper); const checkboxes = findSelectCheckboxes();
const selectAll = findSelectAllCheckbox(wrapper); const selectAll = findSelectAllCheckbox();
selectAll.trigger('click'); selectAll.trigger('click');
selectAll.trigger('click'); selectAll.trigger('click');
...@@ -123,11 +123,11 @@ describe('table registry', () => { ...@@ -123,11 +123,11 @@ describe('table registry', () => {
it('should delete multiple items when multiple items are selected', done => { it('should delete multiple items when multiple items are selected', done => {
const multiDeleteItems = jest.fn().mockResolvedValue(); const multiDeleteItems = jest.fn().mockResolvedValue();
wrapper.setMethods({ multiDeleteItems }); wrapper.setMethods({ multiDeleteItems });
const selectAll = findSelectAllCheckbox(wrapper); const selectAll = findSelectAllCheckbox();
selectAll.trigger('click'); selectAll.trigger('click');
Vue.nextTick(() => { Vue.nextTick(() => {
const deleteBtn = findDeleteButton(wrapper); const deleteBtn = findDeleteButton();
expect(wrapper.vm.selectedItems).toEqual([0, 1]); expect(wrapper.vm.selectedItems).toEqual([0, 1]);
expect(deleteBtn.attributes('disabled')).toEqual(undefined); expect(deleteBtn.attributes('disabled')).toEqual(undefined);
wrapper.setData({ itemsToBeDeleted: [...wrapper.vm.selectedItems] }); wrapper.setData({ itemsToBeDeleted: [...wrapper.vm.selectedItems] });
...@@ -165,8 +165,8 @@ describe('table registry', () => { ...@@ -165,8 +165,8 @@ describe('table registry', () => {
}); });
it('should be possible to delete a registry', () => { it('should be possible to delete a registry', () => {
const deleteBtn = findDeleteButton(wrapper); const deleteBtn = findDeleteButton();
const deleteBtns = findDeleteButtonsRow(wrapper); const deleteBtns = findDeleteButtonsRow();
expect(wrapper.vm.selectedItems).toEqual([0]); expect(wrapper.vm.selectedItems).toEqual([0]);
expect(deleteBtn).toBeDefined(); expect(deleteBtn).toBeDefined();
expect(deleteBtn.attributes('disable')).toBe(undefined); expect(deleteBtn.attributes('disable')).toBe(undefined);
...@@ -174,7 +174,7 @@ describe('table registry', () => { ...@@ -174,7 +174,7 @@ describe('table registry', () => {
}); });
it('should allow deletion row by row', () => { it('should allow deletion row by row', () => {
const deleteBtns = findDeleteButtonsRow(wrapper); const deleteBtns = findDeleteButtonsRow();
const deleteSingleItem = jest.fn(); const deleteSingleItem = jest.fn();
const deleteItem = jest.fn().mockResolvedValue(); const deleteItem = jest.fn().mockResolvedValue();
wrapper.setMethods({ deleteSingleItem, deleteItem }); wrapper.setMethods({ deleteSingleItem, deleteItem });
...@@ -225,11 +225,11 @@ describe('table registry', () => { ...@@ -225,11 +225,11 @@ describe('table registry', () => {
}); });
it('should exist', () => { it('should exist', () => {
const pagination = findPagination(wrapper); const pagination = findPagination();
expect(pagination.exists()).toBe(true); expect(pagination.exists()).toBe(true);
}); });
it('should be visible when pagination is needed', () => { it('should be visible when pagination is needed', () => {
const pagination = findPagination(wrapper); const pagination = findPagination();
expect(pagination.isVisible()).toBe(true); expect(pagination.isVisible()).toBe(true);
wrapper.setProps({ wrapper.setProps({
repo: { repo: {
...@@ -283,22 +283,22 @@ describe('table registry', () => { ...@@ -283,22 +283,22 @@ describe('table registry', () => {
}); });
it('should not render select all', () => { it('should not render select all', () => {
const selectAll = findSelectAllCheckbox(wrapper); const selectAll = findSelectAllCheckbox();
expect(selectAll.exists()).toBe(false); expect(selectAll.exists()).toBe(false);
}); });
it('should not render any select checkbox', () => { it('should not render any select checkbox', () => {
const selects = findSelectCheckboxes(wrapper); const selects = findSelectCheckboxes();
expect(selects.length).toBe(0); expect(selects.length).toBe(0);
}); });
it('should not render delete registry button', () => { it('should not render delete registry button', () => {
const deleteBtn = findDeleteButton(wrapper); const deleteBtn = findDeleteButton();
expect(deleteBtn.exists()).toBe(false); expect(deleteBtn.exists()).toBe(false);
}); });
it('should not render delete row button', () => { it('should not render delete row button', () => {
const deleteBtns = findDeleteButtonsRow(wrapper); const deleteBtns = findDeleteButtonsRow();
expect(deleteBtns.length).toBe(0); expect(deleteBtns.length).toBe(0);
}); });
}); });
......
...@@ -29,7 +29,7 @@ describe('Release detail component', () => { ...@@ -29,7 +29,7 @@ describe('Release detail component', () => {
const store = new Vuex.Store({ actions, state }); const store = new Vuex.Store({ actions, state });
wrapper = mount(ReleaseDetailApp, { store }); wrapper = mount(ReleaseDetailApp, { store, sync: false, attachToDocument: true });
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
......
...@@ -23,7 +23,7 @@ exports[`SidebarTodo template renders component container element with proper da ...@@ -23,7 +23,7 @@ exports[`SidebarTodo template renders component container element with proper da
<span <span
class="issuable-todo-inner" class="issuable-todo-inner"
> >
Mark as done Mark as done
</span> </span>
<glloadingicon-stub <glloadingicon-stub
......
...@@ -89,5 +89,11 @@ describe('SidebarTodo', () => { ...@@ -89,5 +89,11 @@ describe('SidebarTodo', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
}); });
it('hides button icon when `isActionActive` prop is true', () => {
createComponent({ collapsed: true, isActionActive: true });
expect(wrapper.find(Icon).isVisible()).toBe(false);
});
}); });
}); });
...@@ -23,7 +23,8 @@ describe ProjectAutoDevops do ...@@ -23,7 +23,8 @@ describe ProjectAutoDevops do
[ [
{ key: 'INCREMENTAL_ROLLOUT_MODE', value: 'manual' }, { key: 'INCREMENTAL_ROLLOUT_MODE', value: 'manual' },
{ key: 'STAGING_ENABLED', value: '1' }, { key: 'STAGING_ENABLED', value: '1' },
{ key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1' } { key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1' },
{ key: 'AUTO_DEVOPS_EXPLICITLY_ENABLED', value: '1' }
] ]
end end
...@@ -33,6 +34,8 @@ describe ProjectAutoDevops do ...@@ -33,6 +34,8 @@ describe ProjectAutoDevops do
context 'when deploy_strategy is continuous' do context 'when deploy_strategy is continuous' do
let(:auto_devops) { build_stubbed(:project_auto_devops, :continuous_deployment, project: project) } let(:auto_devops) { build_stubbed(:project_auto_devops, :continuous_deployment, project: project) }
it { expect(auto_devops.predefined_variables).to include(key: 'AUTO_DEVOPS_EXPLICITLY_ENABLED', value: '1') }
it do it do
expect(auto_devops.predefined_variables.map { |var| var[:key] }) expect(auto_devops.predefined_variables.map { |var| var[:key] })
.not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED") .not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED")
...@@ -44,11 +47,19 @@ describe ProjectAutoDevops do ...@@ -44,11 +47,19 @@ describe ProjectAutoDevops do
it { expect(auto_devops.predefined_variables).to include(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'timed') } it { expect(auto_devops.predefined_variables).to include(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'timed') }
it { expect(auto_devops.predefined_variables).to include(key: 'AUTO_DEVOPS_EXPLICITLY_ENABLED', value: '1') }
it do it do
expect(auto_devops.predefined_variables.map { |var| var[:key] }) expect(auto_devops.predefined_variables.map { |var| var[:key] })
.not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED") .not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED")
end end
end end
context 'when auto-devops is explicitly disabled' do
let(:auto_devops) { build_stubbed(:project_auto_devops, :disabled, project: project) }
it { expect(auto_devops.predefined_variables.to_hash).to be_empty }
end
end end
describe '#create_gitlab_deploy_token' do describe '#create_gitlab_deploy_token' do
......
...@@ -11,6 +11,7 @@ describe Git::BaseHooksService do ...@@ -11,6 +11,7 @@ describe Git::BaseHooksService do
let(:oldrev) { Gitlab::Git::BLANK_SHA } let(:oldrev) { Gitlab::Git::BLANK_SHA }
let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0 let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
let(:ref) { 'refs/tags/v1.1.0' } let(:ref) { 'refs/tags/v1.1.0' }
let(:checkout_sha) { '5937ac0a7beb003549fc5fd26fc247adbce4a52e' }
let(:test_service) do let(:test_service) do
Class.new(described_class) do Class.new(described_class) do
...@@ -131,4 +132,104 @@ describe Git::BaseHooksService do ...@@ -131,4 +132,104 @@ describe Git::BaseHooksService do
end end
end end
end end
describe 'Generating CI variables from push options' do
let(:pipeline_params) do
{
after: newrev,
before: oldrev,
checkout_sha: checkout_sha,
push_options: push_options, # defined in each context
ref: ref,
variables_attributes: variables_attributes # defined in each context
}
end
shared_examples 'creates pipeline with params and expected variables' do
it 'calls the create pipeline service' do
expect(Ci::CreatePipelineService)
.to receive(:new)
.with(project, user, pipeline_params)
.and_return(double(execute!: true))
subject.execute
end
end
context 'with empty push options' do
let(:push_options) { {} }
let(:variables_attributes) { [] }
it_behaves_like 'creates pipeline with params and expected variables'
end
context 'with push options not specifying variables' do
let(:push_options) do
{
mr: {
create: true
}
}
end
let(:variables_attributes) { [] }
before do
params[:push_options] = push_options
end
it_behaves_like 'creates pipeline with params and expected variables'
end
context 'with push options specifying variables' do
let(:push_options) do
{
ci: {
variable: {
"FOO=123": 1,
"BAR=456": 1,
"MNO=890=ABC": 1
}
}
}
end
let(:variables_attributes) do
[
{ "key" => "FOO", "variable_type" => "env_var", "secret_value" => "123" },
{ "key" => "BAR", "variable_type" => "env_var", "secret_value" => "456" },
{ "key" => "MNO", "variable_type" => "env_var", "secret_value" => "890=ABC" }
]
end
before do
params[:push_options] = push_options
end
it_behaves_like 'creates pipeline with params and expected variables'
end
context 'with push options not specifying variables in correct format' do
let(:push_options) do
{
ci: {
variable: {
"FOO=123": 1,
"BAR": 1,
"=MNO": 1
}
}
}
end
let(:variables_attributes) do
[
{ "key" => "FOO", "variable_type" => "env_var", "secret_value" => "123" }
]
end
before do
params[:push_options] = push_options
end
it_behaves_like 'creates pipeline with params and expected variables'
end
end
end end
...@@ -86,6 +86,7 @@ describe Git::BranchPushService, services: true do ...@@ -86,6 +86,7 @@ describe Git::BranchPushService, services: true do
after: newrev, after: newrev,
ref: ref, ref: ref,
checkout_sha: SeedRepo::Commit::ID, checkout_sha: SeedRepo::Commit::ID,
variables_attributes: [],
push_options: {} push_options: {}
}).and_call_original }).and_call_original
......
...@@ -722,10 +722,10 @@ ...@@ -722,10 +722,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.82.0.tgz#c059c460afc13ebfe9df370521ca8963fa5afb80" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.82.0.tgz#c059c460afc13ebfe9df370521ca8963fa5afb80"
integrity sha512-9L4Brys2LCk44lHvFsCFDKN768lYjoMVYDb4PD7FSjqUEruQQ1SRj0rvb1RWKLhiTCDKrtDOXkH6I1TTEms24w== integrity sha512-9L4Brys2LCk44lHvFsCFDKN768lYjoMVYDb4PD7FSjqUEruQQ1SRj0rvb1RWKLhiTCDKrtDOXkH6I1TTEms24w==
"@gitlab/ui@7.15.1": "@gitlab/ui@7.15.2":
version "7.15.1" version "7.15.2"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-7.15.1.tgz#e58682f729ef428f24129b1897e5e5a41fb68e75" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-7.15.2.tgz#924c202ea43ad79032d91d803665b1f7b8f0a42e"
integrity sha512-YJstpP2jN5C5meZLx5gxkwXXGADc7yrrBdPWZyon2Kj0gQ5wnMjDqlac1FuoIuaKUoAQUMFRT2CsaMwrKPgkMA== integrity sha512-XNrs2iH8waHk/LDp3sTUSlq3vASHUL4WwCiKwoPJP7PZyXZvvumrkNmiDS0ZvPRPB3ZvIrSywRf61sL0PiQZEA==
dependencies: dependencies:
"@babel/standalone" "^7.0.0" "@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0" "@gitlab/vue-toasted" "^1.3.0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment