...@@ -1328,7 +1328,7 @@ DEPENDENCIES ...@@ -1328,7 +1328,7 @@ DEPENDENCIES
request_store (~> 1.3) request_store (~> 1.3)
responders (~> 3.0) responders (~> 3.0)
retriable (~> 3.1.2) retriable (~> 3.1.2)
rouge (~> 3.11.0) rouge (~> 3.15.0)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-parameterized rspec-parameterized
rspec-rails (~> 4.0.0.beta3) rspec-rails (~> 4.0.0.beta3)
... ...
......
---
title: Document CI job activity limit for pipeline creation
merge_request: 23246
author:
type: added
...@@ -42,3 +42,35 @@ Activity history for projects and individuals' profiles was limited to one year ...@@ -42,3 +42,35 @@ Activity history for projects and individuals' profiles was limited to one year
A maximum number of project webhooks applies to each GitLab.com tier. Check the A maximum number of project webhooks applies to each GitLab.com tier. Check the
[Maximum number of webhooks (per tier)](../user/project/integrations/webhooks.md#maximum-number-of-webhooks-per-tier) [Maximum number of webhooks (per tier)](../user/project/integrations/webhooks.md#maximum-number-of-webhooks-per-tier)
section in the Webhooks page. section in the Webhooks page.
## CI/CD limits
### Number of jobs in active pipelines
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/32823) in GitLab 12.6.
The total number of jobs in active pipelines can be limited per project. This limit is checked
each time a new pipeline is created. An active pipeline is any pipeline in one of the following states:
- `created`
- `pending`
- `running`
If a new pipeline would cause the total number of jobs to exceed the limit, the pipeline
will fail with a `job_activity_limit_exceeded` error.
- On GitLab.com different [limits are defined per plan](../user/gitlab_com/index.md#gitlab-cicd) and they affect all projects under that plan.
- On [GitLab Starter](https://about.gitlab.com/pricing/#self-managed) tier or higher self-hosted installations, this limit is defined for the `default` plan that affects all projects.
This limit is disabled by default.
To set this limit on a self-hosted installation, run the following in the
[GitLab Rails console](https://docs.gitlab.com/omnibus/maintenance/#starting-a-rails-console-session):
```ruby
# If limits don't exist for the default plan, you can create one with:
# Plan.default.create_limits!
Plan.default.limits.update!(ci_active_jobs: 500)
```
Set the limit to `0` to disable it.
...@@ -121,7 +121,9 @@ In this example we can see that server processed an HTTP request with URL ...@@ -121,7 +121,9 @@ In this example we can see that server processed an HTTP request with URL
## `api_json.log` ## `api_json.log`
Introduced in GitLab 10.0, this file lives in > Introduced in GitLab 10.0.
This file lives in
`/var/log/gitlab/gitlab-rails/api_json.log` for Omnibus GitLab packages or in `/var/log/gitlab/gitlab-rails/api_json.log` for Omnibus GitLab packages or in
`/home/git/gitlab/log/api_json.log` for installations from source. `/home/git/gitlab/log/api_json.log` for installations from source.
...@@ -159,6 +161,21 @@ October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was ...@@ -159,6 +161,21 @@ October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was
October 07, 2014 11:25: Project "project133" was removed October 07, 2014 11:25: Project "project133" was removed
``` ```
## `application_json.log`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/22812) in GitLab 12.7.
This file lives in `/var/log/gitlab/gitlab-rails/application_json.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/application_json.log` for
installations from source.
It contains the JSON version of the logs in `application.log` like the example below:
``` json
{"severity":"INFO","time":"2020-01-14T13:35:15.466Z","correlation_id":"3823a1550b64417f9c9ed8ee0f48087e","message":"User \"Administrator\" (admin@example.com) was created"}
{"severity":"INFO","time":"2020-01-14T13:35:15.466Z","correlation_id":"78e3df10c9a18745243d524540bd5be4","message":"Project \"project133\" was removed"}
```
## `integrations_json.log` ## `integrations_json.log`
This file lives in `/var/log/gitlab/gitlab-rails/integrations_json.log` for This file lives in `/var/log/gitlab/gitlab-rails/integrations_json.log` for
...@@ -174,7 +191,9 @@ It contains information about [integrations](../user/project/integrations/projec ...@@ -174,7 +191,9 @@ It contains information about [integrations](../user/project/integrations/projec
## `kubernetes.log` ## `kubernetes.log`
Introduced in GitLab 11.6. This file lives in > Introduced in GitLab 11.6.
This file lives in
`/var/log/gitlab/gitlab-rails/kubernetes.log` for Omnibus GitLab `/var/log/gitlab/gitlab-rails/kubernetes.log` for Omnibus GitLab
packages or in `/home/git/gitlab/log/kubernetes.log` for packages or in `/home/git/gitlab/log/kubernetes.log` for
installations from source. installations from source.
...@@ -320,13 +339,17 @@ It logs information whenever a [repository check is run][repocheck] on a project ...@@ -320,13 +339,17 @@ It logs information whenever a [repository check is run][repocheck] on a project
## `importer.log` ## `importer.log`
Introduced in GitLab 11.3. This file lives in `/var/log/gitlab/gitlab-rails/importer.log` for > Introduced in GitLab 11.3.
This file lives in `/var/log/gitlab/gitlab-rails/importer.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/importer.log` for Omnibus GitLab packages or in `/home/git/gitlab/log/importer.log` for
installations from source. installations from source.
## `auth.log` ## `auth.log`
Introduced in GitLab 12.0. This file lives in `/var/log/gitlab/gitlab-rails/auth.log` for > Introduced in GitLab 12.0.
This file lives in `/var/log/gitlab/gitlab-rails/auth.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/auth.log` for Omnibus GitLab packages or in `/home/git/gitlab/log/auth.log` for
installations from source. installations from source.
...@@ -356,7 +379,9 @@ GraphQL queries are recorded in that file. For example: ...@@ -356,7 +379,9 @@ GraphQL queries are recorded in that file. For example:
## `migrations.log` ## `migrations.log`
Introduced in GitLab 12.3. This file lives in `/var/log/gitlab/gitlab-rails/migrations.log` for > Introduced in GitLab 12.3.
This file lives in `/var/log/gitlab/gitlab-rails/migrations.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/migrations.log` for Omnibus GitLab packages or in `/home/git/gitlab/log/migrations.log` for
installations from source. installations from source.
...@@ -406,7 +431,9 @@ It is stored at: ...@@ -406,7 +431,9 @@ It is stored at:
## `elasticsearch.log` ## `elasticsearch.log`
Introduced in GitLab 12.6. This file lives in > Introduced in GitLab 12.6.
This file lives in
`/var/log/gitlab/gitlab-rails/elasticsearch.log` for Omnibus GitLab `/var/log/gitlab/gitlab-rails/elasticsearch.log` for Omnibus GitLab
packages or in `/home/git/gitlab/log/elasticsearch.log` for installations packages or in `/home/git/gitlab/log/elasticsearch.log` for installations
from source. from source.
... ...
......
...@@ -75,6 +75,7 @@ Below are the current settings regarding [GitLab CI/CD](../../ci/README.md). ...@@ -75,6 +75,7 @@ Below are the current settings regarding [GitLab CI/CD](../../ci/README.md).
| Artifacts maximum size (uncompressed) | 1G | 100M | | Artifacts maximum size (uncompressed) | 1G | 100M |
| Artifacts [expiry time](../../ci/yaml/README.md#artifactsexpire_in) | kept forever | deleted after 30 days unless otherwise specified | | Artifacts [expiry time](../../ci/yaml/README.md#artifactsexpire_in) | kept forever | deleted after 30 days unless otherwise specified |
| Scheduled Pipeline Cron | `*/5 * * * *` | `*/19 * * * *` | | Scheduled Pipeline Cron | `*/5 * * * *` | `*/19 * * * *` |
| [Max jobs in active pipelines](../../administration/instance_limits.md#number-of-jobs-in-active-pipelines) | `500` for Free tier, unlimited otherwise | Unlimited
## Repository size limit ## Repository size limit
...@@ -95,7 +96,11 @@ IP based firewall can be configured by looking up all ...@@ -95,7 +96,11 @@ IP based firewall can be configured by looking up all
## Shared Runners ## Shared Runners
Shared Runners on GitLab.com run in [autoscale mode] and powered by Google Cloud Platform. GitLab offers Linux and Windows shared runners hosted on GitLab.com for executing your pipelines.
### Linux Shared Runners
Linux Shared Runners on GitLab.com run in [autoscale mode] and are powered by Google Cloud Platform.
Autoscaling means reduced waiting times to spin up CI/CD jobs, and isolated VMs for each project, Autoscaling means reduced waiting times to spin up CI/CD jobs, and isolated VMs for each project,
thus maximizing security. They're free to use for public open source projects and limited thus maximizing security. They're free to use for public open source projects and limited
to 2000 CI minutes per month per group for private projects. More minutes to 2000 CI minutes per month per group for private projects. More minutes
...@@ -122,7 +127,7 @@ Below are the shared Runners settings. ...@@ -122,7 +127,7 @@ Below are the shared Runners settings.
| Default Docker image | `ruby:2.5` | - | | Default Docker image | `ruby:2.5` | - |
| `privileged` (run [Docker in Docker]) | `true` | `false` | | `privileged` (run [Docker in Docker]) | `true` | `false` |
### `config.toml` #### `config.toml`
The full contents of our `config.toml` are: The full contents of our `config.toml` are:
...@@ -184,6 +189,158 @@ sentry_dsn = "X" ...@@ -184,6 +189,158 @@ sentry_dsn = "X"
BucketName = "bucket-name" BucketName = "bucket-name"
``` ```
### Windows Shared Runners (beta)
The Windows Shared Runners are currently in
[beta](https://about.gitlab.com/handbook/product/#beta) and should not be used
for production workloads.
During the beta period for groups and private projects the use of
Windows Shared Runners will count towards the [shared runner pipeline
quota](https://docs.gitlab.com/ee/user/admin_area/settings/continuous_integration.html#shared-runners-pipeline-minutes-quota-starter-only)
as if they are Linux Runners, we do have plans to change this in
[#30835](https://gitlab.com/gitlab-org/gitlab/issues/30834).
Windows Shared Runners on GitLab.com automatically autoscale by
launching virtual machines on the Google Cloud Platform. This solution uses
a new [autoscaling driver](https://gitlab.com/gitlab-org/ci-cd/custom-executor-drivers/autoscaler/tree/master/docs/readme.md)
developed by GitLab for the [custom executor](https://docs.gitlab.com/runner/executors/custom.html).
Windows Shared Runners execute your CI/CD jobs on `n1-standard-2` instances with 2
vCPUs and 7.5GB RAM. You can find a full list of available Windows packages in the
[package documentation](https://gitlab.com/gitlab-org/ci-cd/shared-runners/images/gcp/windows-containers/blob/master/cookbooks/preinstalled-software/README.md).
We want to keep iterating to get Windows Shared Runners in a stable state and
[generally available](https://about.gitlab.com/handbook/product/#generally-available-ga).
You can follow our work towards this goal in the
[related epic](https://gitlab.com/groups/gitlab-org/-/epics/2162).
#### Configuration
The full contents of our `config.toml` are:
```toml
concurrent = 10
check_interval = 3
[[runners]]
name = "windows-runner"
url = "https://gitlab.com/"
token = "TOKEN"
executor = "custom"
builds_dir = "C:\\GitLab-Runner\\builds"
cache_dir = "C:\\GitLab-Runner\\cache"
shell = "powershell"
[runners.custom]
config_exec = "C:\\GitLab-Runner\\autoscaler\\autoscaler.exe"
config_args = ["--config", "C:\\GitLab-Runner\\autoscaler\\config.toml", "custom", "config"]
prepare_exec = "C:\\GitLab-Runner\\autoscaler\\autoscaler.exe"
prepare_args = ["--config", "C:\\GitLab-Runner\\autoscaler\\config.toml", "custom", "prepare"]
run_exec = "C:\\GitLab-Runner\\autoscaler\\autoscaler.exe"
run_args = ["--config", "C:\\GitLab-Runner\\autoscaler\\config.toml", "custom", "run"]
cleanup_exec = "C:\\GitLab-Runner\\autoscaler\\autoscaler.exe"
cleanup_args = ["--config", "C:\\GitLab-Runner\\autoscaler\\config.toml", "custom", "cleanup"]
```
The full contents of our `autoscaler/config.toml` are:
```toml
Provider = "gcp"
Executor = "winrm"
OS = "windows"
LogLevel = "info"
LogFormat = "text"
LogFile = "C:\\GitLab-Runner\\autoscaler\\autoscaler.log"
VMTag = "windows"
[GCP]
ServiceAccountFile = "PATH"
Project = "some-project-df9323"
Zone = "us-east1-c"
MachineType = "n1-standard-2"
Image = "IMAGE"
DiskSize = 50
DiskType = "pd-standard"
Subnetwork = "default"
Network = "default"
Tags = ["TAGS"]
Username = "gitlab_runner"
[WinRM]
MaximumTimeout = 3600
ExecutionMaxRetries = 0
[ProviderCache]
Enabled = true
Directory = "C:\\GitLab-Runner\\autoscaler\\machines"
```
#### Example
Below is a simple `.gitlab-ci.yml` file to show how to start using the
Windows Shared Runners:
```yaml
.shared_windows_runners:
tags:
- shared
- windows
- windows-1809
stages:
- build
- test
before_script:
- date +"%H"
- echo ${HOUR}
- echo "started by ${GITLAB_USER_NAME}"
build:
extends:
- .shared_windows_runners
stage: build
script:
- echo "running scripts in the build job"
test:
extends:
- .shared_windows_runners
stage: test
script:
- echo "running scripts in the test job"
```
#### Limitations and known issues
- All the limitations mentioned in our [beta
definition](https://about.gitlab.com/handbook/product/#beta).
- The average provisioning time for a new Windows VM is 5 minutes.
This means that for the beta you will notice slower build start times
on the Windows Shared Runner fleet compared to Linux. In a future
release we will add the ability to the autoscaler which will enable
the pre-warming of virtual machines. This will significantly reduce
the time it takes to provision a VM on the Windows fleet. You can
follow along in this
[issue](https://gitlab.com/gitlab-org/ci-cd/custom-executor-drivers/autoscaler/issues/32).
- The Windows Shared Runner fleet may be unavailable occasionally
for maintenance or updates.
- The Windows Shared Runner virtual machine instances do not use the
GitLab Docker executor. This means that unlike the Linux Shared
Runners, you will not be able to specify `image` and `services` in
your pipeline configuration.
- For the beta release, we have included a set of software packages in
the base VM image. If your CI job requires additional software that's
not included in this list, then you will need to add installation
commands to [`before_script`](../../ci/yaml/README.md#before_script-and-after_script) or [`script`](../../ci/yaml/README.md#script) to install the required
software. Note that each job runs on a new VM instance, so the
installation of additional software packages needs to be repeated for
each job in your pipeline.
- The job may stay in a pending state for longer than the
Linux shared Runners.
- There is the possibility that we introduce breaking changes which will
require updates to pipelines that are using the Windows Shared Runner
fleet.
## Sidekiq ## Sidekiq
GitLab.com runs [Sidekiq](https://sidekiq.org) with arguments `--timeout=4 --concurrency=4` GitLab.com runs [Sidekiq](https://sidekiq.org) with arguments `--timeout=4 --concurrency=4`
... ...
......
...@@ -54,8 +54,8 @@ Design Management requires that projects are using ...@@ -54,8 +54,8 @@ Design Management requires that projects are using
- Design Management data [won't be moved](https://gitlab.com/gitlab-org/gitlab/issues/13426) - Design Management data [won't be moved](https://gitlab.com/gitlab-org/gitlab/issues/13426)
when an issue is moved, nor [deleted](https://gitlab.com/gitlab-org/gitlab/issues/13427) when an issue is moved, nor [deleted](https://gitlab.com/gitlab-org/gitlab/issues/13427)
when an issue is deleted. when an issue is deleted.
- Design Management - From GitLab 12.7, Design Management data [can be replicated](../../../administration/geo/replication/datatypes.md#limitations-on-replicationverification)
[isn't supported by Geo](https://gitlab.com/groups/gitlab-org/-/epics/1633) yet. by Geo but [not verified](https://gitlab.com/gitlab-org/gitlab/issues/32467).
- Only the latest version of the designs can be deleted. - Only the latest version of the designs can be deleted.
- Deleted designs cannot be recovered but you can see them on previous designs versions. - Deleted designs cannot be recovered but you can see them on previous designs versions.
... ...
......
...@@ -8,7 +8,7 @@ import TimeSeries from '~/monitoring/components/charts/time_series.vue'; ...@@ -8,7 +8,7 @@ import TimeSeries from '~/monitoring/components/charts/time_series.vue';
import * as types from '~/monitoring/stores/mutation_types'; import * as types from '~/monitoring/stores/mutation_types';
import { import {
deploymentData, deploymentData,
metricsGroupsAPIResponse, metricsDashboardPayload,
mockedQueryResultPayload, mockedQueryResultPayload,
mockProjectDir, mockProjectDir,
mockHost, mockHost,
...@@ -34,7 +34,7 @@ describe('Time series component', () => { ...@@ -34,7 +34,7 @@ describe('Time series component', () => {
store.commit( store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
metricsGroupsAPIResponse, metricsDashboardPayload,
); );
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
... ...
......
...@@ -14,9 +14,8 @@ import { createStore } from '~/monitoring/stores'; ...@@ -14,9 +14,8 @@ import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types'; import * as types from '~/monitoring/stores/mutation_types';
import { setupComponentStore, propsData } from '../init_utils'; import { setupComponentStore, propsData } from '../init_utils';
import { import {
metricsGroupsAPIResponse, metricsDashboardPayload,
mockedQueryResultPayload, mockedQueryResultPayload,
mockApiEndpoint,
environmentData, environmentData,
dashboardGitResponse, dashboardGitResponse,
} from '../mock_data'; } from '../mock_data';
...@@ -33,6 +32,9 @@ describe('Dashboard', () => { ...@@ -33,6 +32,9 @@ describe('Dashboard', () => {
wrapper = shallowMount(Dashboard, { wrapper = shallowMount(Dashboard, {
localVue, localVue,
propsData: { ...propsData, ...props }, propsData: { ...propsData, ...props },
methods: {
fetchData: jest.fn(),
},
store, store,
...options, ...options,
}); });
...@@ -42,6 +44,9 @@ describe('Dashboard', () => { ...@@ -42,6 +44,9 @@ describe('Dashboard', () => {
wrapper = mount(Dashboard, { wrapper = mount(Dashboard, {
localVue, localVue,
propsData: { ...propsData, ...props }, propsData: { ...propsData, ...props },
methods: {
fetchData: jest.fn(),
},
store, store,
...options, ...options,
}); });
...@@ -55,21 +60,16 @@ describe('Dashboard', () => { ...@@ -55,21 +60,16 @@ describe('Dashboard', () => {
afterEach(() => { afterEach(() => {
if (wrapper) { if (wrapper) {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
} }
mock.restore(); mock.restore();
}); });
describe('no metrics are available yet', () => { describe('no metrics are available yet', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
createShallowWrapper(); createShallowWrapper();
}); });
afterEach(() => {
wrapper.destroy();
});
it('shows the environment selector', () => { it('shows the environment selector', () => {
expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
}); });
...@@ -77,29 +77,19 @@ describe('Dashboard', () => { ...@@ -77,29 +77,19 @@ describe('Dashboard', () => {
describe('no data found', () => { describe('no data found', () => {
beforeEach(done => { beforeEach(done => {
mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
createShallowWrapper(); createShallowWrapper();
wrapper.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
afterEach(() => {
wrapper.destroy();
});
it('shows the environment selector dropdown', () => { it('shows the environment selector dropdown', () => {
expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
}); });
}); });
describe('request information to the server', () => { describe('request information to the server', () => {
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
});
it('shows up a loading state', done => { it('shows up a loading state', done => {
createShallowWrapper({ hasMetrics: true }); createShallowWrapper({ hasMetrics: true }, { methods: {} });
wrapper.vm wrapper.vm
.$nextTick() .$nextTick()
...@@ -153,17 +143,11 @@ describe('Dashboard', () => { ...@@ -153,17 +143,11 @@ describe('Dashboard', () => {
describe('when all requests have been commited by the store', () => { describe('when all requests have been commited by the store', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
setupComponentStore(wrapper); setupComponentStore(wrapper);
}); });
afterEach(() => {
wrapper.destroy();
});
it('renders the environments dropdown with a number of environments', done => { it('renders the environments dropdown with a number of environments', done => {
wrapper.vm wrapper.vm
.$nextTick() .$nextTick()
...@@ -211,7 +195,7 @@ describe('Dashboard', () => { ...@@ -211,7 +195,7 @@ describe('Dashboard', () => {
wrapper.vm.$store.commit( wrapper.vm.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
metricsGroupsAPIResponse, metricsDashboardPayload,
); );
wrapper.vm.$store.commit( wrapper.vm.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
...@@ -247,8 +231,6 @@ describe('Dashboard', () => { ...@@ -247,8 +231,6 @@ describe('Dashboard', () => {
describe('when one of the metrics is missing', () => { describe('when one of the metrics is missing', () => {
beforeEach(done => { beforeEach(done => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
createShallowWrapper({ hasMetrics: true }); createShallowWrapper({ hasMetrics: true });
setupComponentStore(wrapper); setupComponentStore(wrapper);
...@@ -278,10 +260,6 @@ describe('Dashboard', () => { ...@@ -278,10 +260,6 @@ describe('Dashboard', () => {
const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel'); const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel');
const findRearrangeButton = () => wrapper.find('.js-rearrange-button'); const findRearrangeButton = () => wrapper.find('.js-rearrange-button');
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
});
beforeEach(done => { beforeEach(done => {
createShallowWrapper({ hasMetrics: true }); createShallowWrapper({ hasMetrics: true });
...@@ -290,10 +268,6 @@ describe('Dashboard', () => { ...@@ -290,10 +268,6 @@ describe('Dashboard', () => {
wrapper.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
afterEach(() => {
wrapper.destroy();
});
it('wraps vuedraggable', () => { it('wraps vuedraggable', () => {
expect(findDraggablePanels().exists()).toBe(true); expect(findDraggablePanels().exists()).toBe(true);
expect(findDraggablePanels().length).toEqual(expectedPanelCount); expect(findDraggablePanels().length).toEqual(expectedPanelCount);
...@@ -332,7 +306,7 @@ describe('Dashboard', () => { ...@@ -332,7 +306,7 @@ describe('Dashboard', () => {
it('metrics can be swapped', done => { it('metrics can be swapped', done => {
const firstDraggable = findDraggables().at(0); const firstDraggable = findDraggables().at(0);
const mockMetrics = [...metricsGroupsAPIResponse.panel_groups[1].panels]; const mockMetrics = [...metricsDashboardPayload.panel_groups[1].panels];
const firstTitle = mockMetrics[0].title; const firstTitle = mockMetrics[0].title;
const secondTitle = mockMetrics[1].title; const secondTitle = mockMetrics[1].title;
...@@ -384,10 +358,6 @@ describe('Dashboard', () => { ...@@ -384,10 +358,6 @@ describe('Dashboard', () => {
wrapper.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
afterEach(() => {
wrapper.destroy();
});
it('renders correctly', () => { it('renders correctly', () => {
expect(wrapper.isVueInstance()).toBe(true); expect(wrapper.isVueInstance()).toBe(true);
expect(wrapper.exists()).toBe(true); expect(wrapper.exists()).toBe(true);
...@@ -398,8 +368,6 @@ describe('Dashboard', () => { ...@@ -398,8 +368,6 @@ describe('Dashboard', () => {
const findEditLink = () => wrapper.find('.js-edit-link'); const findEditLink = () => wrapper.find('.js-edit-link');
beforeEach(done => { beforeEach(done => {
mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
createShallowWrapper({ hasMetrics: true }); createShallowWrapper({ hasMetrics: true });
wrapper.vm.$store.commit( wrapper.vm.$store.commit(
...@@ -409,10 +377,6 @@ describe('Dashboard', () => { ...@@ -409,10 +377,6 @@ describe('Dashboard', () => {
wrapper.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
afterEach(() => {
wrapper.destroy();
});
it('is not present for the default dashboard', () => { it('is not present for the default dashboard', () => {
expect(findEditLink().exists()).toBe(false); expect(findEditLink().exists()).toBe(false);
}); });
...@@ -435,8 +399,6 @@ describe('Dashboard', () => { ...@@ -435,8 +399,6 @@ describe('Dashboard', () => {
describe('Dashboard dropdown', () => { describe('Dashboard dropdown', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
wrapper.vm.$store.commit( wrapper.vm.$store.commit(
...@@ -460,8 +422,6 @@ describe('Dashboard', () => { ...@@ -460,8 +422,6 @@ describe('Dashboard', () => {
describe('external dashboard link', () => { describe('external dashboard link', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
createMountedWrapper( createMountedWrapper(
{ {
hasMetrics: true, hasMetrics: true,
...@@ -497,17 +457,11 @@ describe('Dashboard', () => { ...@@ -497,17 +457,11 @@ describe('Dashboard', () => {
const clipboardText = () => link().element.dataset.clipboardText; const clipboardText = () => link().element.dataset.clipboardText;
beforeEach(done => { beforeEach(done => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
createShallowWrapper({ hasMetrics: true, currentDashboard }); createShallowWrapper({ hasMetrics: true, currentDashboard });
setTimeout(done); setTimeout(done);
}); });
afterEach(() => {
wrapper.destroy();
});
it('adds a copy button to the dropdown', () => { it('adds a copy button to the dropdown', () => {
expect(link().text()).toContain('Generate link to chart'); expect(link().text()).toContain('Generate link to chart');
}); });
... ...
......
...@@ -6,7 +6,7 @@ import statusCodes from '~/lib/utils/http_status'; ...@@ -6,7 +6,7 @@ import statusCodes from '~/lib/utils/http_status';
import Dashboard from '~/monitoring/components/dashboard.vue'; import Dashboard from '~/monitoring/components/dashboard.vue';
import { createStore } from '~/monitoring/stores'; import { createStore } from '~/monitoring/stores';
import { propsData, setupComponentStore } from '../init_utils'; import { propsData, setupComponentStore } from '../init_utils';
import { metricsGroupsAPIResponse, mockApiEndpoint } from '../mock_data'; import { metricsDashboardPayload, mockApiEndpoint } from '../mock_data';
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
getParameterValues: jest.fn().mockImplementation(param => { getParameterValues: jest.fn().mockImplementation(param => {
...@@ -43,7 +43,7 @@ describe('dashboard time window', () => { ...@@ -43,7 +43,7 @@ describe('dashboard time window', () => {
}); });
it('shows an error message if invalid url parameters are passed', done => { it('shows an error message if invalid url parameters are passed', done => {
mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse); mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsDashboardPayload);
createComponentWrapperMounted({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); createComponentWrapperMounted({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
... ...
......
import * as types from '~/monitoring/stores/mutation_types'; import * as types from '~/monitoring/stores/mutation_types';
import { import {
metricsGroupsAPIResponse, metricsDashboardPayload,
mockedEmptyResult, mockedEmptyResult,
mockedQueryResultPayload, mockedQueryResultPayload,
mockedQueryResultPayloadCoresTotal, mockedQueryResultPayloadCoresTotal,
...@@ -23,7 +23,7 @@ export const propsData = { ...@@ -23,7 +23,7 @@ export const propsData = {
emptyNoDataSvgPath: '/path/to/no-data.svg', emptyNoDataSvgPath: '/path/to/no-data.svg',
emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg', emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg',
emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
environmentsEndpoint: '/root/hello-prometheus/environments/35', environmentsEndpoint: '/root/hello-prometheus/-/environments.json',
currentEnvironmentName: 'production', currentEnvironmentName: 'production',
customMetricsAvailable: false, customMetricsAvailable: false,
customMetricsPath: '', customMetricsPath: '',
...@@ -33,7 +33,7 @@ export const propsData = { ...@@ -33,7 +33,7 @@ export const propsData = {
export const setupComponentStore = wrapper => { export const setupComponentStore = wrapper => {
wrapper.vm.$store.commit( wrapper.vm.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
metricsGroupsAPIResponse, metricsDashboardPayload,
); );
// Load 3 panels to the dashboard, one with an empty result // Load 3 panels to the dashboard, one with an empty result
... ...
......
...@@ -331,81 +331,6 @@ export const mockedQueryResultPayloadCoresTotal = { ...@@ -331,81 +331,6 @@ export const mockedQueryResultPayloadCoresTotal = {
], ],
}; };
export const metricsGroupsAPIResponse = {
dashboard: 'Environment metrics',
panel_groups: [
{
group: 'Response metrics (NGINX Ingress VTS)',
priority: 10,
panels: [
{
metrics: [
{
id: 'response_metrics_nginx_ingress_throughput_status_code',
label: 'Status Code',
metric_id: 1,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29',
query_range:
'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)',
unit: 'req / sec',
},
],
title: 'Throughput',
type: 'area-chart',
weight: 1,
y_label: 'Requests / Sec',
},
],
},
{
group: 'System metrics (Kubernetes)',
priority: 5,
panels: [
{
title: 'Memory Usage (Pod average)',
type: 'area-chart',
y_label: 'Memory Used per Pod',
weight: 2,
metrics: [
{
id: 'system_metrics_kubernetes_container_memory_average',
query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024',
label: 'Pod average',
unit: 'MB',
metric_id: 17,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024',
appearance: {
line: {
width: 2,
},
},
},
],
},
{
title: 'Core Usage (Total)',
type: 'area-chart',
y_label: 'Total Cores',
weight: 3,
metrics: [
{
id: 'system_metrics_kubernetes_container_cores_total',
query_range:
'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)',
label: 'Total',
unit: 'cores',
metric_id: 13,
},
],
},
],
},
],
};
export const environmentData = [ export const environmentData = [
{ {
id: 34, id: 34,
...@@ -517,6 +442,81 @@ export const metricsDashboardResponse = { ...@@ -517,6 +442,81 @@ export const metricsDashboardResponse = {
status: 'success', status: 'success',
}; };
export const metricsDashboardPayload = {
dashboard: 'Environment metrics',
panel_groups: [
{
group: 'Response metrics (NGINX Ingress VTS)',
priority: 10,
panels: [
{
metrics: [
{
id: 'response_metrics_nginx_ingress_throughput_status_code',
label: 'Status Code',
metric_id: 1,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29',
query_range:
'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)',
unit: 'req / sec',
},
],
title: 'Throughput',
type: 'area-chart',
weight: 1,
y_label: 'Requests / Sec',
},
],
},
{
group: 'System metrics (Kubernetes)',
priority: 5,
panels: [
{
title: 'Memory Usage (Pod average)',
type: 'area-chart',
y_label: 'Memory Used per Pod',
weight: 2,
metrics: [
{
id: 'system_metrics_kubernetes_container_memory_average',
query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024',
label: 'Pod average',
unit: 'MB',
metric_id: 17,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024',
appearance: {
line: {
width: 2,
},
},
},
],
},
{
title: 'Core Usage (Total)',
type: 'area-chart',
y_label: 'Total Cores',
weight: 3,
metrics: [
{
id: 'system_metrics_kubernetes_container_cores_total',
query_range:
'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)',
label: 'Total',
unit: 'cores',
metric_id: 13,
},
],
},
],
},
],
};
export const dashboardGitResponse = [ export const dashboardGitResponse = [
{ {
default: true, default: true,
... ...
......
...@@ -25,7 +25,7 @@ import { ...@@ -25,7 +25,7 @@ import {
deploymentData, deploymentData,
environmentData, environmentData,
metricsDashboardResponse, metricsDashboardResponse,
metricsGroupsAPIResponse, metricsDashboardPayload,
dashboardGitResponse, dashboardGitResponse,
} from '../mock_data'; } from '../mock_data';
...@@ -442,7 +442,7 @@ describe('Monitoring store actions', () => { ...@@ -442,7 +442,7 @@ describe('Monitoring store actions', () => {
beforeEach(() => { beforeEach(() => {
state = storeState(); state = storeState();
[metric] = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics; [metric] = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics;
[data] = metricsGroupsAPIResponse.panel_groups[0].panels[0].metrics; [data] = metricsDashboardPayload.panel_groups[0].panels[0].metrics;
}); });
it('commits result', done => { it('commits result', done => {
... ...
......
...@@ -3,7 +3,7 @@ import mutations from '~/monitoring/stores/mutations'; ...@@ -3,7 +3,7 @@ import mutations from '~/monitoring/stores/mutations';
import * as types from '~/monitoring/stores/mutation_types'; import * as types from '~/monitoring/stores/mutation_types';
import { metricStates } from '~/monitoring/constants'; import { metricStates } from '~/monitoring/constants';
import { import {
metricsGroupsAPIResponse, metricsDashboardPayload,
mockedEmptyResult, mockedEmptyResult,
mockedQueryResultPayload, mockedQueryResultPayload,
mockedQueryResultPayloadCoresTotal, mockedQueryResultPayloadCoresTotal,
...@@ -44,7 +44,7 @@ describe('Monitoring store Getters', () => { ...@@ -44,7 +44,7 @@ describe('Monitoring store Getters', () => {
setupState({ setupState({
dashboard: { panel_groups: [] }, dashboard: { panel_groups: [] },
}); });
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
groups = state.dashboard.panel_groups; groups = state.dashboard.panel_groups;
}); });
...@@ -53,21 +53,21 @@ describe('Monitoring store Getters', () => { ...@@ -53,21 +53,21 @@ describe('Monitoring store Getters', () => {
}); });
it('on an empty metric with no result, returns NO_DATA', () => { it('on an empty metric with no result, returns NO_DATA', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedEmptyResult); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedEmptyResult);
expect(getMetricStates()).toEqual([metricStates.NO_DATA]); expect(getMetricStates()).toEqual([metricStates.NO_DATA]);
}); });
it('on a metric with a result, returns OK', () => { it('on a metric with a result, returns OK', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
expect(getMetricStates()).toEqual([metricStates.OK]); expect(getMetricStates()).toEqual([metricStates.OK]);
}); });
it('on a metric with an error, returns an error', () => { it('on a metric with an error, returns an error', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, {
metricId: groups[0].panels[0].metrics[0].metricId, metricId: groups[0].panels[0].metrics[0].metricId,
}); });
...@@ -76,7 +76,7 @@ describe('Monitoring store Getters', () => { ...@@ -76,7 +76,7 @@ describe('Monitoring store Getters', () => {
}); });
it('on multiple metrics with results, returns OK', () => { it('on multiple metrics with results, returns OK', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal);
...@@ -87,7 +87,7 @@ describe('Monitoring store Getters', () => { ...@@ -87,7 +87,7 @@ describe('Monitoring store Getters', () => {
expect(getMetricStates(state.dashboard.panel_groups[1].key)).toEqual([metricStates.OK]); expect(getMetricStates(state.dashboard.panel_groups[1].key)).toEqual([metricStates.OK]);
}); });
it('on multiple metrics errors', () => { it('on multiple metrics errors', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, { mutations[types.RECEIVE_METRIC_RESULT_FAILURE](state, {
metricId: groups[0].panels[0].metrics[0].metricId, metricId: groups[0].panels[0].metrics[0].metricId,
...@@ -106,7 +106,7 @@ describe('Monitoring store Getters', () => { ...@@ -106,7 +106,7 @@ describe('Monitoring store Getters', () => {
}); });
it('on multiple metrics with errors', () => { it('on multiple metrics with errors', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
// An success in 1 group // An success in 1 group
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
...@@ -168,27 +168,27 @@ describe('Monitoring store Getters', () => { ...@@ -168,27 +168,27 @@ describe('Monitoring store Getters', () => {
}); });
it('no loaded metric returns empty', () => { it('no loaded metric returns empty', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
expect(metricsWithData()).toEqual([]); expect(metricsWithData()).toEqual([]);
}); });
it('an empty metric, returns empty', () => { it('an empty metric, returns empty', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedEmptyResult); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedEmptyResult);
expect(metricsWithData()).toEqual([]); expect(metricsWithData()).toEqual([]);
}); });
it('a metric with results, it returns a metric', () => { it('a metric with results, it returns a metric', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
expect(metricsWithData()).toEqual([mockedQueryResultPayload.metricId]); expect(metricsWithData()).toEqual([mockedQueryResultPayload.metricId]);
}); });
it('multiple metrics with results, it return multiple metrics', () => { it('multiple metrics with results, it return multiple metrics', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal);
...@@ -199,7 +199,7 @@ describe('Monitoring store Getters', () => { ...@@ -199,7 +199,7 @@ describe('Monitoring store Getters', () => {
}); });
it('multiple metrics with results, it returns metrics filtered by group', () => { it('multiple metrics with results, it returns metrics filtered by group', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsGroupsAPIResponse); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](state, metricsDashboardPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayload);
mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal); mutations[types.RECEIVE_METRIC_RESULT_SUCCESS](state, mockedQueryResultPayloadCoresTotal);
... ...
......
...@@ -5,7 +5,7 @@ import * as types from '~/monitoring/stores/mutation_types'; ...@@ -5,7 +5,7 @@ import * as types from '~/monitoring/stores/mutation_types';
import state from '~/monitoring/stores/state'; import state from '~/monitoring/stores/state';
import { metricStates } from '~/monitoring/constants'; import { metricStates } from '~/monitoring/constants';
import { import {
metricsGroupsAPIResponse, metricsDashboardPayload,
deploymentData, deploymentData,
metricsDashboardResponse, metricsDashboardResponse,
dashboardGitResponse, dashboardGitResponse,
...@@ -23,7 +23,7 @@ describe('Monitoring mutations', () => { ...@@ -23,7 +23,7 @@ describe('Monitoring mutations', () => {
beforeEach(() => { beforeEach(() => {
stateCopy.dashboard.panel_groups = []; stateCopy.dashboard.panel_groups = [];
payload = metricsGroupsAPIResponse; payload = metricsDashboardPayload;
}); });
it('adds a key to the group', () => { it('adds a key to the group', () => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload);
... ...
......
...@@ -6,7 +6,7 @@ import * as types from '~/monitoring/stores/mutation_types'; ...@@ -6,7 +6,7 @@ import * as types from '~/monitoring/stores/mutation_types';
import { createStore } from '~/monitoring/stores'; import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { import {
metricsGroupsAPIResponse, metricsDashboardPayload,
mockedEmptyResult, mockedEmptyResult,
mockedQueryResultPayload, mockedQueryResultPayload,
mockedQueryResultPayloadCoresTotal, mockedQueryResultPayloadCoresTotal,
...@@ -41,7 +41,7 @@ function setupComponentStore(component) { ...@@ -41,7 +41,7 @@ function setupComponentStore(component) {
// Load 2 panel groups // Load 2 panel groups
component.$store.commit( component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
metricsGroupsAPIResponse, metricsDashboardPayload,
); );
// Load 3 panels to the dashboard, one with an empty result // Load 3 panels to the dashboard, one with an empty result
...@@ -98,7 +98,7 @@ describe('Dashboard', () => { ...@@ -98,7 +98,7 @@ describe('Dashboard', () => {
let panelToggle; let panelToggle;
let chart; let chart;
beforeEach(() => { beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); mock.onGet(mockApiEndpoint).reply(200, metricsDashboardPayload);
component = new DashboardComponent({ component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'), el: document.querySelector('.prometheus-graphs'),
... ...
......
...@@ -5,6 +5,14 @@ require 'spec_helper' ...@@ -5,6 +5,14 @@ require 'spec_helper'
describe Gitlab::Email::Receiver do describe Gitlab::Email::Receiver do
include_context :email_shared_context include_context :email_shared_context
shared_examples 'correctly finds the mail key' do
specify do
expect(Gitlab::Email::Handler).to receive(:for).with(an_instance_of(Mail::Message), 'gitlabhq/gitlabhq+auth_token').and_return(handler)
receiver.execute
end
end
context 'when the email contains a valid email address in a header' do context 'when the email contains a valid email address in a header' do
let(:handler) { double(:handler) } let(:handler) { double(:handler) }
... ...
......
...@@ -16,6 +16,40 @@ describe Gitlab::MailRoom do ...@@ -16,6 +16,40 @@ describe Gitlab::MailRoom do
} }
end end
shared_examples_for 'only truthy if both enabled and address are truthy' do |target_proc|
context 'with both enabled and address as truthy values' do
it 'is truthy' do
stub_config(enabled: true, address: 'localhost')
expect(target_proc.call).to be_truthy
end
end
context 'with address only as truthy' do
it 'is falsey' do
stub_config(enabled: false, address: 'localhost')
expect(target_proc.call).to be_falsey
end
end
context 'with enabled only as truthy' do
it 'is falsey' do
stub_config(enabled: true, address: nil)
expect(target_proc.call).to be_falsey
end
end
context 'with neither address nor enabled as truthy' do
it 'is falsey' do
stub_config(enabled: false, address: nil)
expect(target_proc.call).to be_falsey
end
end
end
before do before do
described_class.reset_config! described_class.reset_config!
allow(File).to receive(:exist?).and_return true allow(File).to receive(:exist?).and_return true
... ...
......
# frozen_string_literal: true # frozen_string_literal: true
require 'fast_spec_helper' require 'fast_spec_helper'
require 'support/shared_examples/malicious_regexp_shared_examples' require 'support/shared_examples/lib/gitlab/malicious_regexp_shared_examples'
require 'support/helpers/stub_feature_flags' require 'support/helpers/stub_feature_flags'
describe Gitlab::UntrustedRegexp::RubySyntax do describe Gitlab::UntrustedRegexp::RubySyntax do
... ...
......
# frozen_string_literal: true # frozen_string_literal: true
require 'fast_spec_helper' require 'fast_spec_helper'
require 'support/shared_examples/malicious_regexp_shared_examples' require 'support/shared_examples/lib/gitlab/malicious_regexp_shared_examples'
describe Gitlab::UntrustedRegexp do describe Gitlab::UntrustedRegexp do
describe '#initialize' do describe '#initialize' do
... ...
......
...@@ -27,7 +27,42 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do ...@@ -27,7 +27,42 @@ describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
let(:build) { create(:ci_build, :running, :trace_live, pipeline: pipeline, project: parent) } let(:build) { create(:ci_build, :running, :trace_live, pipeline: pipeline, project: parent) }
let(:subjects) { build.trace_chunks } let(:subjects) { build.trace_chunks }
it_behaves_like 'fast destroyable' describe 'Forbid #destroy and #destroy_all' do
it 'does not delete database rows and associted external data' do
expect(external_data_counter).to be > 0
expect(subjects.count).to be > 0
expect { subjects.first.destroy }.to raise_error('`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`')
expect { subjects.destroy_all }.to raise_error('`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`') # rubocop: disable DestroyAll
expect(subjects.count).to be > 0
expect(external_data_counter).to be > 0
end
end
describe '.fast_destroy_all' do
it 'deletes database rows and associted external data' do
expect(external_data_counter).to be > 0
expect(subjects.count).to be > 0
expect { subjects.fast_destroy_all }.not_to raise_error
expect(subjects.count).to eq(0)
expect(external_data_counter).to eq(0)
end
end
describe '.use_fast_destroy' do
it 'performs cascading delete with fast_destroy_all' do
expect(external_data_counter).to be > 0
expect(subjects.count).to be > 0
expect { parent.destroy }.not_to raise_error
expect(subjects.count).to eq(0)
expect(external_data_counter).to eq(0)
end
end
def external_data_counter def external_data_counter
Gitlab::Redis::SharedState.with do |redis| Gitlab::Redis::SharedState.with do |redis|
... ...
......