Skip to content
Snippets Groups Projects
Commit c282dba8 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 2c2dd5e3
No related branches found
No related tags found
No related merge requests found
Showing
with 634 additions and 58 deletions
Loading
Loading
@@ -138,6 +138,47 @@ Example of using a token:
docker login registry.example.com -u <username> -p <token>
```
 
## Expiration policy
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/15398) in GitLab 12.8.
It is possible to create a per-project expiration policy, so that you can make sure that
older tags and images are regularly removed from the Container Registry.
### Managing project expiration policy through the API
You can set, update, and disable the expiration policies using the GitLab API.
Examples:
- Select all tags, keep at least 1 tag per image, expire any tag older than 14 days, run once a month, and the policy is enabled:
```bash
curl --request PUT --header 'Content-Type: application/json;charset=UTF-8' --header "PRIVATE-TOKEN: <your_access_token>" --data-binary '{"container_expiration_policy_attributes":{"cadence":"1month","enabled":true,"keep_n":1,"older_than":"14d","name_regex":".*"}' 'https://gitlab.example.com/api/v4/projects/2'
```
- Select only tags with a name that contains `stable`, keep at least 50 tag per image, expire any tag older than 7 days, run every day, and the policy is enabled:
```bash
curl --request PUT --header 'Content-Type: application/json;charset=UTF-8' --header "PRIVATE-TOKEN: <your_access_token>" --data-binary '{"container_expiration_policy_attributes":{"cadence":"1day","enabled":true,"keep_n":50"older_than":"7d","name_regex":"*stable"}' 'https://gitlab.example.com/api/v4/projects/2'
```
See the API documentation for further details: [Edit project](../../../api/projects.md#edit-project).
### Managing project expiration policy through the UI
To manage project expiration policy, navigate to **Settings > CI/CD > Container Registry tag expiration policy**.
![Expiration Policy App](img/expiration-policy-app.png)
The UI allows you to configure the following:
- **Expiration policy:** enable or disable the expiration policy.
- **Expiration interval:** how long tags are exempt from being deleted.
- **Expiration schedule:** how often the cron job checking the tags should run.
- **Expiration latest:** how many tags to _always_ keep for each image.
- **Expire Docker tags with regex matching:** the regex used to determine what tags should be expired. To qualify all tags for expiration, use the default value of `.*`.
## Troubleshooting the GitLab Container Registry
 
### Docker connection error
Loading
Loading
Loading
Loading
@@ -27,4 +27,5 @@ NOTE: **Note** We are especially interested in adding support for [PyPi](https:/
 
Learning how to use the GitLab Package Registry will help you build your own custom package workflow.
 
[Use a project as a package registry](./workflows/project_registry.md) to publish all of your packages to one project.
- [Use a project as a package registry](./workflows/project_registry.md) to publish all of your packages to one project.
- [Working with a monorepo](./workflows/monorepo.md): Learn how to publish multiple different packages from one monorepo project.
Loading
Loading
@@ -338,3 +338,53 @@ The next time the `deploy` job runs, it will copy `ci_settings.xml` to the
user's home location (in this case the user is `root` since it runs in a
Docker container), and Maven will utilize the configured CI
[environment variables](../../../ci/variables/README.md#predefined-environment-variables).
## Troubleshooting
### Useful Maven command line options
There's some [maven command line options](https://maven.apache.org/ref/current/maven-embedder/cli.html)
which maybe useful when doing tasks with GitLab CI.
- File transfer progress can make the CI logs hard to read.
Option `-ntp,--no-transfer-progress` was added in
[3.6.1](https://maven.apache.org/docs/3.6.1/release-notes.html#User_visible_Changes).
Alternatively, look at `-B,--batch-mode`
[or lower level logging changes.](https://stackoverflow.com/questions/21638697/disable-maven-download-progress-indication)
- Specify where to find the POM file (`-f,--file`):
```yaml
package:
script:
- 'mvn --no-transfer-progress -f helloworld/pom.xml package'
```
- Specify where to find the user settings (`-s,--settings`) instead of
[the default location](https://maven.apache.org/settings.html). There's also a `-gs,--global-settings` option:
```yaml
package:
script:
- 'mvn -s settings/ci.xml package'
```
### Verifying your Maven settings
If you encounter issues within CI that relate to the `settings.xml` file, it might be useful
to add an additional script task or job to
[verify the effective settings](https://maven.apache.org/plugins/maven-help-plugin/effective-settings-mojo.html).
The help plugin can also provide
[system properties](https://maven.apache.org/plugins/maven-help-plugin/system-mojo.html), including environment variables:
```yaml
mvn-settings:
script:
- 'mvn help:effective-settings'
package:
script:
- 'mvn help:system'
- 'mvn package'
```
# Monorepo package management workflows
Oftentimes, one project or Git repository may contain multiple different
subprojects or submodules that all get packaged and published individually.
## Publishing different packages to the parent project
The number and name of packages you can publish to one project is not limited.
You can accomplish this by setting up different configuration files for each
package. See the documentation for the package manager of your choice since
each will have its own specific files and instructions to follow to publish
a given package.
Here, we will walk through how to do this with [NPM](../npm_registry/index.md).
Let us say we have a project structure like so:
```plaintext
MyProject/
|- src/
| |- components/
| |- Foo/
|- package.json
```
`MyProject` is the parent project, which contains a sub-project `Foo` in the
`components` directory. We would like to publish packages for both `MyProject`
as well as `Foo`.
Following the instructions in the
[GitLab NPM registry documentation](../npm_registry/index.md),
publishing `MyProject` consists of modifying the `package.json` file with a
`publishConfig` section, as well as either modifying your local NPM config with
CLI commands like `npm config set`, or saving a `.npmrc` file in the root of the
project specifying these config settings.
If you follow the instructions you can publish `MyProject` by running
`npm publish` from the root directory.
Publishing `Foo` is almost exactly the same, you simply have to follow the steps
while in the `Foo` directory. `Foo` will need it's own `package.json` file,
which can be added manually or using `npm init`. And it will need it's own
configuration settings. Since you are publishing to the same place, if you
used `npm config set` to set the registry for the parent project, then no
additional setup is necessary. If you used a `.npmrc` file, you will need an
additional `.npmrc` file in the `Foo` directory (be sure to add `.npmrc` files
to the `.gitignore` file or use environment variables in place of your access
tokens to preven them from being exposed). It can be identical to the
one you used in `MyProject`. You can now run `npm publish` from the `Foo`
directory and you will be able to publish `Foo` separately from `MyProject`
A similar process could be followed for Conan packages, instead of dealing with
`.npmrc` and `package.json`, you will just be dealing with `conanfile.py` in
multiple locations within the project.
## Publishing to other projects
A package is associated with a project on GitLab, but the package does not
need to be associated with the code in that project. Notice when configuring
NPM or Maven, you only use the `Project ID` to set the registry URL that the
package will be uploaded to. If you set this to any project that you have
access to and update any other config similarly depending on the package type,
your packages will be published to that project. This means you can publish
multiple packages to one project, even if their code does not exist in the same
place. See the [project registry workflow documentation](./project_registry.md)
for more details.
## CI workflows for automating packaging
CI pipelines open an entire world of possibilities for dealing with the patterns
described in the previous sections. A common desire would be to publish
specific packages only if changes were made to those directories.
Using the example project above, this `gitlab-ci.yml` file will publish
`Foo` anytime changes are made to the `Foo` directory on the `master` branch,
and publish `MyPackage` anytime changes are made to anywhere _except_ the `Foo`
directory on the `master` branch.
```sh
stages:
- build
.default-rule: &default-rule
if: '$CI_MERGE_REQUEST_IID || $CI_COMMIT_REF_SLUG == "master"'
.foo-package:
variables:
PACKAGE: "Foo"
before_script:
- cd src/components/Foo
only:
changes:
- "src/components/Foo/**/*"
.parent-package:
variables:
PACKAGE: "MyPackage"
except:
changes:
- "src/components/Foo/**/*"
.build-package:
stage: build
script:
- echo "Building $PACKAGE"
- npm publish
rules:
- <<: *default-rule
build-foo-package:
extends:
- .build-package
- .foo-package
build-my-project-package:
extends:
- .build-package
- .parent-package
```
Loading
Loading
@@ -177,6 +177,31 @@ namespace if needed.
 
[permissions]: ../../permissions.md#project-members-permissions
 
#### Remove a project
NOTE: **Note:**
Only project owners and admins have [permissions]((../../permissions.md#project-members-permissions) to remove a project.
To remove a project:
1. Navigate to your project, and select **{settings}** **Settings > General > Advanced**.
1. In the Remove project section, click the **Remove project** button.
1. Confirm the action when asked to.
This action either:
- Removes a project including all associated resources (issues, merge requests etc).
- Since [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/issues/32935), on [Premium or Silver](https://about.gitlab.com/pricing/) or higher tiers, marks a project for deletion. The deletion will happen 7 days later by default, but this can be changed in the [instance settings](../../admin_area/settings/visibility_and_access_controls.md#default-deletion-adjourned-period-premium-only).
### Restore a project **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/32935) in GitLab 12.6.
To restore a project that is marked for deletion:
1. Navigate to your project, and select **{settings}** **Settings > General > Advanced**.
1. In the Restore project section, click the **Restore project** button.
## Operations settings
 
### Error Tracking
Loading
Loading
Loading
Loading
@@ -34,26 +34,55 @@ module Gitlab
def extract_commands(content, only: nil)
return [content, []] unless content
 
content = content.dup
content, commands = perform_regex(content, only: only)
 
commands = []
perform_substitutions(content, commands)
end
 
# Encloses quick action commands into code span markdown
# avoiding them being executed, for example, when sent via email
# to GitLab service desk.
# Example: /label ~label1 becomes `/label ~label1`
def redact_commands(content)
return "" unless content
content, _ = perform_regex(content, redact: true)
content
end
private
def perform_regex(content, only: nil, redact: false)
commands = []
content = content.dup
content.delete!("\r")
content.gsub!(commands_regex(only: only)) do
if $~[:cmd]
commands << [$~[:cmd].downcase, $~[:arg]].reject(&:blank?)
''
else
$~[0]
end
command, output = process_commands($~, redact)
commands << command
output
end
 
content, commands = perform_substitutions(content, commands)
[content.rstrip, commands]
[content.rstrip, commands.reject(&:empty?)]
end
 
private
def process_commands(matched_text, redact)
output = matched_text[0]
command = []
if matched_text[:cmd]
command = [matched_text[:cmd].downcase, matched_text[:arg]].reject(&:blank?)
output = ''
if redact
output = "`/#{matched_text[:cmd]}#{" " + matched_text[:arg] if matched_text[:arg]}`"
output += "\n" if matched_text[0].include?("\n")
end
end
[command, output]
end
 
# Builds a regular expression to match known commands.
# First match group captures the command name and
Loading
Loading
Loading
Loading
@@ -528,6 +528,9 @@ msgstr ""
msgid "+ %{numberOfHiddenAssignees} more"
msgstr ""
 
msgid "+%{approvers} more approvers"
msgstr ""
msgid "+%{extraOptionCount} more"
msgstr ""
 
Loading
Loading
@@ -5053,6 +5056,9 @@ msgstr ""
msgid "ContainerRegistry|Copy push command"
msgstr ""
 
msgid "ContainerRegistry|Currently, the Container Registry tag expiration feature is not available for projects created before GitLab version 12.8. For updates and more information, visit Issue %{linkStart}#196124%{linkEnd}"
msgstr ""
msgid "ContainerRegistry|Docker connection error"
msgstr ""
 
Loading
Loading
@@ -9809,6 +9815,9 @@ msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
 
msgid "Here you will find recent merge request activity"
msgstr ""
msgid "Hi %{username}!"
msgstr ""
 
Loading
Loading
@@ -12421,6 +12430,9 @@ msgstr ""
msgid "No application_settings found"
msgstr ""
 
msgid "No approvers"
msgstr ""
msgid "No authentication methods configured."
msgstr ""
 
Loading
Loading
@@ -13176,9 +13188,15 @@ msgstr ""
msgid "Pagination|Last »"
msgstr ""
 
msgid "Pagination|Next"
msgstr ""
msgid "Pagination|Next ›"
msgstr ""
 
msgid "Pagination|Prev"
msgstr ""
msgid "Pagination|« First"
msgstr ""
 
Loading
Loading
@@ -15947,9 +15965,15 @@ msgstr ""
msgid "Restart Terminal"
msgstr ""
 
msgid "Restore group"
msgstr ""
msgid "Restore project"
msgstr ""
 
msgid "Restoring the group will prevent the group, its subgroups and projects from being removed on this date."
msgstr ""
msgid "Restoring the project will prevent the project from being removed on this date and restore people's ability to make changes to it."
msgstr ""
 
Loading
Loading
@@ -18549,6 +18573,9 @@ msgstr ""
msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
msgstr ""
 
msgid "The contents of this group, its subgroups and projects will be permanently removed after %{deletion_adjourned_period} days on %{date}. After this point, your data cannot be recovered."
msgstr ""
msgid "The current issue"
msgstr ""
 
Loading
Loading
@@ -18611,12 +18638,18 @@ msgstr ""
msgid "The group and its projects can only be viewed by members."
msgstr ""
 
msgid "The group can be fully restored"
msgstr ""
msgid "The group has already been shared with this group"
msgstr ""
 
msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
msgstr ""
 
msgid "The group will be placed in 'pending removal' state"
msgstr ""
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
 
Loading
Loading
@@ -19100,9 +19133,18 @@ msgstr ""
msgid "This group does not provide any group Runners yet."
msgstr ""
 
msgid "This group has been scheduled for permanent removal on %{date}"
msgstr ""
msgid "This group, including all subgroups, projects and git repositories, will only be reachable from the specified IP address range. Multiple addresses are supported with comma delimiters.<br>Example: <code>192.168.0.0/24,192.168.1.0/24</code>. %{read_more_link}."
msgstr ""
 
msgid "This group, its subgroups and projects has been scheduled for removal on %{date}."
msgstr ""
msgid "This group, its subgroups and projects will be removed on %{date} since its parent group '%{parent_group_name}'' has been scheduled for removal."
msgstr ""
msgid "This is a \"Ghost User\", created to hold all issues authored by users that have since been deleted. This user cannot be removed."
msgstr ""
 
Loading
Loading
@@ -19274,6 +19316,9 @@ msgstr ""
msgid "This project will be removed on %{date}"
msgstr ""
 
msgid "This project will be removed on %{date} since its parent group '%{parent_group_name}' has been scheduled for removal."
msgstr ""
msgid "This repository"
msgstr ""
 
Loading
Loading
@@ -20286,6 +20331,9 @@ msgstr ""
msgid "Uploads"
msgstr ""
 
msgid "Upon performing this action, the contents of this group, its subgroup and projects will be permanently removed after %{deletion_adjourned_period} days on <strong>%{date}</strong>. Until that time:"
msgstr ""
msgid "Upstream"
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -63,7 +63,7 @@ describe 'Profile > Password' do
 
visit edit_profile_password_path
 
expect(page).to have_gitlab_http_status(404)
expect(page).to have_gitlab_http_status(:not_found)
end
end
 
Loading
Loading
@@ -73,7 +73,7 @@ describe 'Profile > Password' do
it 'renders 404' do
visit edit_profile_password_path
 
expect(page).to have_gitlab_http_status(404)
expect(page).to have_gitlab_http_status(:not_found)
end
end
end
Loading
Loading
Loading
Loading
@@ -54,7 +54,7 @@ describe 'test coverage badge' do
it 'user requests test coverage badge image' do
show_test_coverage_badge
 
expect(page).to have_gitlab_http_status(404)
expect(page).to have_gitlab_http_status(:not_found)
end
end
 
Loading
Loading
Loading
Loading
@@ -4,20 +4,26 @@ require 'spec_helper'
 
describe 'Project > Settings > CI/CD > Container registry tag expiration policy', :js do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
let(:project) { create(:project, namespace: user.namespace, container_registry_enabled: container_registry_enabled) }
let(:container_registry_enabled) { true }
before do
sign_in(user)
stub_container_registry_config(enabled: true)
stub_feature_flags(registry_retention_policies_settings: true)
end
 
context 'as owner' do
before do
sign_in(user)
visit project_settings_ci_cd_path(project)
end
 
it 'section is available' do
it 'shows available section' do
settings_block = find('#js-registry-policies')
expect(settings_block).to have_text 'Container Registry tag expiration policy'
end
 
it 'Save expiration policy submit the form' do
it 'saves expiration policy submit the form' do
within '#js-registry-policies' do
within '.card-body' do
find('#expiration-policy-toggle button:not(.is-disabled)').click
Loading
Loading
@@ -34,4 +40,38 @@ describe 'Project > Settings > CI/CD > Container registry tag expiration policy'
expect(toast).to have_content('Expiration policy successfully saved.')
end
end
context 'when registry is disabled' do
before do
stub_container_registry_config(enabled: false)
visit project_settings_ci_cd_path(project)
end
it 'does not exists' do
expect(page).not_to have_selector('#js-registry-policies')
end
end
context 'when container registry is disabled on project' do
let(:container_registry_enabled) { false }
before do
visit project_settings_ci_cd_path(project)
end
it 'does not exists' do
expect(page).not_to have_selector('#js-registry-policies')
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(registry_retention_policies_settings: false)
visit project_settings_ci_cd_path(project)
end
it 'does not exists' do
expect(page).not_to have_selector('#js-registry-policies')
end
end
end
Loading
Loading
@@ -7,7 +7,7 @@ describe 'User views tags', :feature do
it do
visit project_tags_path(project, format: :atom)
 
expect(page).to have_gitlab_http_status(200)
expect(page).to have_gitlab_http_status(:ok)
end
end
 
Loading
Loading
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
describe('getIdFromGraphQLId', () => {
[
{
input: '',
output: null,
},
{
input: null,
output: null,
},
{
input: 'gid://',
output: null,
},
{
input: 'gid://gitlab/',
output: null,
},
{
input: 'gid://gitlab/Environments',
output: null,
},
{
input: 'gid://gitlab/Environments/',
output: null,
},
{
input: 'gid://gitlab/Environments/123',
output: 123,
},
{
input: 'gid://gitlab/DesignManagement::Version/2',
output: 2,
},
].forEach(({ input, output }) => {
it(`getIdFromGraphQLId returns ${output} when passed ${input}`, () => {
expect(getIdFromGraphQLId(input)).toBe(output);
});
});
});
Loading
Loading
@@ -332,7 +332,7 @@ export const mockedQueryResultPayloadCoresTotal = {
};
 
const extraEnvironmentData = new Array(15).fill(null).map((_, idx) => ({
id: 136 + idx,
id: `gid://gitlab/Environments/${150 + idx}`,
name: `no-deployment/noop-branch-${idx}`,
state: 'available',
created_at: '2018-07-04T18:39:41.702Z',
Loading
Loading
@@ -341,7 +341,7 @@ const extraEnvironmentData = new Array(15).fill(null).map((_, idx) => ({
 
export const environmentData = [
{
id: 34,
id: 'gid://gitlab/Environments/34',
name: 'production',
state: 'available',
external_url: 'http://root-autodevops-deploy.my-fake-domain.com',
Loading
Loading
@@ -359,7 +359,7 @@ export const environmentData = [
},
},
{
id: 35,
id: 'gid://gitlab/Environments/35',
name: 'review/noop-branch',
state: 'available',
external_url: 'http://root-autodevops-deploy-review-noop-branc-die93w.my-fake-domain.com',
Loading
Loading
Loading
Loading
@@ -20,6 +20,7 @@ import {
setGettingStartedEmptyState,
duplicateSystemDashboard,
} from '~/monitoring/stores/actions';
import { gqClient, parseEnvironmentsResponse } from '~/monitoring/stores/utils';
import storeState from '~/monitoring/stores/state';
import {
deploymentData,
Loading
Loading
@@ -105,37 +106,46 @@ describe('Monitoring store actions', () => {
});
});
describe('fetchEnvironmentsData', () => {
it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on error', done => {
it('commits RECEIVE_ENVIRONMENTS_DATA_SUCCESS on error', () => {
const dispatch = jest.fn();
const { state } = store;
state.environmentsEndpoint = '/success';
mock.onGet(state.environmentsEndpoint).reply(200, {
environments: environmentData,
});
fetchEnvironmentsData({
state.projectPath = '/gitlab-org/gitlab-test';
jest.spyOn(gqClient, 'mutate').mockReturnValue(
Promise.resolve({
data: {
project: {
data: {
environments: environmentData,
},
},
},
}),
);
return fetchEnvironmentsData({
state,
dispatch,
})
.then(() => {
expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataSuccess', environmentData);
done();
})
.catch(done.fail);
}).then(() => {
expect(dispatch).toHaveBeenCalledWith(
'receiveEnvironmentsDataSuccess',
parseEnvironmentsResponse(environmentData, state.projectPath),
);
});
});
it('commits RECEIVE_ENVIRONMENTS_DATA_FAILURE on error', done => {
it('commits RECEIVE_ENVIRONMENTS_DATA_FAILURE on error', () => {
const dispatch = jest.fn();
const { state } = store;
state.environmentsEndpoint = '/error';
mock.onGet(state.environmentsEndpoint).reply(500);
fetchEnvironmentsData({
state.projectPath = '/gitlab-org/gitlab-test';
jest.spyOn(gqClient, 'mutate').mockReturnValue(Promise.reject());
return fetchEnvironmentsData({
state,
dispatch,
})
.then(() => {
expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataFailure');
done();
})
.catch(done.fail);
}).then(() => {
expect(dispatch).toHaveBeenCalledWith('receiveEnvironmentsDataFailure');
});
});
});
describe('Set endpoints', () => {
Loading
Loading
import { normalizeMetric, uniqMetricsId } from '~/monitoring/stores/utils';
import {
normalizeMetric,
uniqMetricsId,
parseEnvironmentsResponse,
removeLeadingSlash,
} from '~/monitoring/stores/utils';
const projectPath = 'gitlab-org/gitlab-test';
 
describe('normalizeMetric', () => {
[
Loading
Loading
@@ -32,3 +39,71 @@ describe('uniqMetricsId', () => {
});
});
});
describe('parseEnvironmentsResponse', () => {
[
{
input: null,
output: [],
},
{
input: undefined,
output: [],
},
{
input: [],
output: [],
},
{
input: [
{
id: '1',
name: 'env-1',
},
],
output: [
{
id: 1,
name: 'env-1',
metrics_path: `${projectPath}/environments/1/metrics`,
},
],
},
{
input: [
{
id: 'gid://gitlab/Environment/12',
name: 'env-12',
},
],
output: [
{
id: 12,
name: 'env-12',
metrics_path: `${projectPath}/environments/12/metrics`,
},
],
},
].forEach(({ input, output }) => {
it(`parseEnvironmentsResponse returns ${JSON.stringify(output)} with input ${JSON.stringify(
input,
)}`, () => {
expect(parseEnvironmentsResponse(input, projectPath)).toEqual(output);
});
});
});
describe('removeLeadingSlash', () => {
[
{ input: null, output: '' },
{ input: '', output: '' },
{ input: 'gitlab-org', output: 'gitlab-org' },
{ input: 'gitlab-org/gitlab', output: 'gitlab-org/gitlab' },
{ input: '/gitlab-org/gitlab', output: 'gitlab-org/gitlab' },
{ input: '////gitlab-org/gitlab', output: 'gitlab-org/gitlab' },
].forEach(({ input, output }) => {
it(`removeLeadingSlash returns ${output} with input ${input}`, () => {
expect(removeLeadingSlash(input)).toEqual(output);
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui';
import component from '~/registry/settings/components/registry_settings_app.vue';
import SettingsForm from '~/registry/settings/components/settings_form.vue';
import { createStore } from '~/registry/settings/store/';
import { SET_IS_DISABLED } from '~/registry/settings/store/mutation_types';
import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/settings/constants';
 
describe('Registry Settings App', () => {
let wrapper;
let store;
 
const findSettingsComponent = () => wrapper.find({ ref: 'settings-form' });
const findSettingsComponent = () => wrapper.find(SettingsForm);
const findAlert = () => wrapper.find(GlAlert);
 
const mountComponent = ({ dispatchMock } = {}) => {
const mountComponent = ({ dispatchMock = 'mockResolvedValue', isDisabled = false } = {}) => {
store = createStore();
store.commit(SET_IS_DISABLED, isDisabled);
const dispatchSpy = jest.spyOn(store, 'dispatch');
if (dispatchMock) {
dispatchSpy[dispatchMock]();
Loading
Loading
@@ -30,12 +35,12 @@ describe('Registry Settings App', () => {
});
 
it('renders', () => {
mountComponent({ dispatchMock: 'mockResolvedValue' });
mountComponent();
expect(wrapper.element).toMatchSnapshot();
});
 
it('call the store function to load the data on mount', () => {
mountComponent({ dispatchMock: 'mockResolvedValue' });
mountComponent();
expect(store.dispatch).toHaveBeenCalledWith('fetchSettings');
});
 
Loading
Loading
@@ -49,7 +54,21 @@ describe('Registry Settings App', () => {
});
 
it('renders the setting form', () => {
mountComponent({ dispatchMock: 'mockResolvedValue' });
mountComponent();
expect(findSettingsComponent().exists()).toBe(true);
});
describe('isDisabled', () => {
beforeEach(() => {
mountComponent({ isDisabled: true });
});
it('the form is hidden', () => {
expect(findSettingsComponent().exists()).toBe(false);
});
it('shows an alert', () => {
expect(findAlert().exists()).toBe(true);
});
});
});
Loading
Loading
@@ -5,18 +5,38 @@ import * as types from '~/registry/settings/store/mutation_types';
 
describe('Actions Registry Store', () => {
describe.each`
actionName | mutationName | payload
${'setInitialState'} | ${types.SET_INITIAL_STATE} | ${'foo'}
${'updateSettings'} | ${types.UPDATE_SETTINGS} | ${'foo'}
${'receiveSettingsSuccess'} | ${types.SET_SETTINGS} | ${'foo'}
${'toggleLoading'} | ${types.TOGGLE_LOADING} | ${undefined}
${'resetSettings'} | ${types.RESET_SETTINGS} | ${undefined}
actionName | mutationName | payload
${'setInitialState'} | ${types.SET_INITIAL_STATE} | ${'foo'}
${'updateSettings'} | ${types.UPDATE_SETTINGS} | ${'foo'}
${'toggleLoading'} | ${types.TOGGLE_LOADING} | ${undefined}
${'resetSettings'} | ${types.RESET_SETTINGS} | ${undefined}
`('%s action invokes %s mutation with payload %s', ({ actionName, mutationName, payload }) => {
it('should set the initial state', done => {
testAction(actions[actionName], payload, {}, [{ type: mutationName, payload }], [], done);
});
});
 
describe('receiveSettingsSuccess', () => {
it('calls SET_SETTINGS when data is present', () => {
testAction(
actions.receiveSettingsSuccess,
'foo',
{},
[{ type: types.SET_SETTINGS, payload: 'foo' }],
[],
);
});
it('calls SET_IS_DISABLED when data is not present', () => {
testAction(
actions.receiveSettingsSuccess,
null,
{},
[{ type: types.SET_IS_DISABLED, payload: true }],
[],
);
});
});
describe('fetchSettings', () => {
const state = {
projectId: 'bar',
Loading
Loading
Loading
Loading
@@ -32,6 +32,7 @@ describe('Mutations Registry Store', () => {
expect(mockState.settings).toEqual(expectedState.settings);
});
});
describe('SET_SETTINGS', () => {
it('should set the settings and original', () => {
const payload = { foo: 'baz' };
Loading
Loading
@@ -41,6 +42,7 @@ describe('Mutations Registry Store', () => {
expect(mockState.original).toEqual(expectedState.settings);
});
});
describe('RESET_SETTINGS', () => {
it('should copy original over settings', () => {
mockState.settings = { foo: 'bar' };
Loading
Loading
@@ -49,10 +51,18 @@ describe('Mutations Registry Store', () => {
expect(mockState.settings).toEqual(mockState.original);
});
});
describe('TOGGLE_LOADING', () => {
it('should toggle the loading', () => {
mutations[types.TOGGLE_LOADING](mockState);
expect(mockState.isLoading).toEqual(true);
});
});
describe('SET_IS_DISABLED', () => {
it('should set isDisabled', () => {
mutations[types.SET_IS_DISABLED](mockState, true);
expect(mockState.isDisabled).toEqual(true);
});
});
});
Loading
Loading
@@ -155,6 +155,35 @@ describe('GroupItemComponent', () => {
});
 
describe('template', () => {
let group = null;
describe('for a group pending deletion', () => {
beforeEach(() => {
group = { ...mockParentGroupItem, pendingRemoval: true };
vm = createComponent(group);
});
it('renders the group pending removal badge', () => {
const badgeEl = vm.$el.querySelector('.badge-warning');
expect(badgeEl).toBeDefined();
expect(badgeEl).toContainText('pending removal');
});
});
describe('for a group not scheduled for deletion', () => {
beforeEach(() => {
group = { ...mockParentGroupItem, pendingRemoval: false };
vm = createComponent(group);
});
it('does not render the group pending removal badge', () => {
const groupTextContainer = vm.$el.querySelector('.group-text-container');
expect(groupTextContainer).not.toContainText('pending removal');
});
});
it('should render component template correctly', () => {
const visibilityIconEl = vm.$el.querySelector('.item-visibility');
 
Loading
Loading
Loading
Loading
@@ -294,4 +294,22 @@ describe Gitlab::QuickActions::Extractor do
expect(msg).to eq expected_msg
end
end
describe '#redact_commands' do
using RSpec::Parameterized::TableSyntax
where(:text, :expected) do
"hello\n/labels ~label1 ~label2\nworld" | "hello\n`/labels ~label1 ~label2`\nworld"
"hello\n/open\n/labels ~label1\nworld" | "hello\n`/open`\n`/labels ~label1`\nworld"
"hello\n/reopen\nworld" | "hello\n`/reopen`\nworld"
"/reopen\nworld" | "`/reopen`\nworld"
"hello\n/open" | "hello\n`/open`"
end
with_them do
it 'encloses quick actions with code span markdown' do
expect(extractor.redact_commands(text)).to eq(expected)
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment