Commit 6044caed authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 92077e0f
import Vue from 'vue';
import Metrics from '~/monitoring/components/embed.vue';
import { createStore } from '~/monitoring/stores';
import EmbedGroup from '~/monitoring/components/embeds/embed_group.vue';
import { createStore } from '~/monitoring/stores/embed_group/';
// TODO: Handle copy-pasting - https://gitlab.com/gitlab-org/gitlab-foss/issues/64369.
export default function renderMetrics(elements) {
......@@ -8,16 +8,36 @@ export default function renderMetrics(elements) {
return;
}
const EmbedGroupComponent = Vue.extend(EmbedGroup);
const wrapperList = [];
elements.forEach(element => {
const { dashboardUrl } = element.dataset;
const MetricsComponent = Vue.extend(Metrics);
let wrapper;
const { previousElementSibling } = element;
const isFirstElementInGroup = !previousElementSibling?.urls;
if (isFirstElementInGroup) {
wrapper = document.createElement('div');
wrapper.urls = [element.dataset.dashboardUrl];
element.parentNode.insertBefore(wrapper, element);
wrapperList.push(wrapper);
} else {
wrapper = previousElementSibling;
wrapper.urls.push(element.dataset.dashboardUrl);
}
// Clean up processed element
element.parentNode.removeChild(element);
});
wrapperList.forEach(wrapper => {
// eslint-disable-next-line no-new
new MetricsComponent({
el: element,
new EmbedGroupComponent({
el: wrapper,
store: createStore(),
propsData: {
dashboardUrl,
urls: wrapper.urls,
},
});
});
......
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import sum from 'lodash/sum';
import { GlButton, GlCard, GlIcon } from '@gitlab/ui';
import { n__ } from '~/locale';
import { monitoringDashboard } from '~/monitoring/stores';
import MetricEmbed from './metric_embed.vue';
export default {
components: {
GlButton,
GlCard,
GlIcon,
MetricEmbed,
},
props: {
urls: {
type: Array,
required: true,
validator: urls => urls.length > 0,
},
},
data() {
return {
isCollapsed: false,
};
},
computed: {
...mapState('embedGroup', ['module']),
...mapGetters('embedGroup', ['metricsWithData']),
arrowIconName() {
return this.isCollapsed ? 'chevron-right' : 'chevron-down';
},
bodyClass() {
return ['border-top', 'pl-3', 'pt-3', { 'd-none': this.isCollapsed }];
},
buttonLabel() {
return this.isCollapsed
? n__('View chart', 'View charts', this.numCharts)
: n__('Hide chart', 'Hide charts', this.numCharts);
},
containerClass() {
return this.isSingleChart ? 'col-lg-12' : 'col-lg-6';
},
numCharts() {
if (this.metricsWithData === null) {
return 0;
}
return sum(this.metricsWithData);
},
isSingleChart() {
return this.numCharts === 1;
},
},
created() {
this.urls.forEach((url, index) => {
const name = this.getNamespace(index);
this.$store.registerModule(name, monitoringDashboard);
this.addModule(name);
});
},
methods: {
...mapActions('embedGroup', ['addModule']),
getNamespace(id) {
return `monitoringDashboard/${id}`;
},
toggleCollapsed() {
this.isCollapsed = !this.isCollapsed;
},
},
};
</script>
<template>
<gl-card
v-show="numCharts > 0"
class="collapsible-card border p-0 mb-3"
header-class="d-flex align-items-center border-bottom-0 py-2"
:body-class="bodyClass"
>
<template #header>
<gl-button
class="collapsible-card-btn d-flex text-decoration-none"
:aria-label="buttonLabel"
variant="link"
@click="toggleCollapsed"
>
<gl-icon class="mr-1" :name="arrowIconName" />
{{ buttonLabel }}
</gl-button>
</template>
<div class="d-flex flex-wrap">
<metric-embed
v-for="(url, index) in urls"
:key="`${index}/${url}`"
:dashboard-url="url"
:namespace="getNamespace(index)"
:container-class="containerClass"
/>
</div>
</gl-card>
</template>
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
import { mapState, mapActions } from 'vuex';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { timeRangeFromUrl, removeTimeRangeParams } from '../utils';
import { sidebarAnimationDuration } from '../constants';
import { defaultTimeRange } from '~/vue_shared/constants';
import { timeRangeFromUrl, removeTimeRangeParams } from '../../utils';
import { sidebarAnimationDuration } from '../../constants';
let sidebarMutationObserver;
......@@ -13,10 +13,20 @@ export default {
PanelType,
},
props: {
containerClass: {
type: String,
required: false,
default: 'col-lg-12',
},
dashboardUrl: {
type: String,
required: true,
},
namespace: {
type: String,
required: false,
default: 'monitoringDashboard',
},
},
data() {
const timeRange = timeRangeFromUrl(this.dashboardUrl) || defaultTimeRange;
......@@ -26,21 +36,32 @@ export default {
};
},
computed: {
...mapState('monitoringDashboard', ['dashboard']),
...mapGetters('monitoringDashboard', ['metricsWithData']),
...mapState({
dashboard(state) {
return state[this.namespace].dashboard;
},
metricsWithData(state, getters) {
return getters[`${this.namespace}/metricsWithData`]();
},
}),
charts() {
if (!this.dashboard || !this.dashboard.panelGroups) {
return [];
}
const groupWithMetrics = this.dashboard.panelGroups.find(group =>
group.panels.find(chart => this.chartHasData(chart)),
) || { panels: [] };
return groupWithMetrics.panels.filter(chart => this.chartHasData(chart));
return this.dashboard.panelGroups.reduce(
(acc, currentGroup) => acc.concat(currentGroup.panels.filter(this.chartHasData)),
[],
);
},
isSingleChart() {
return this.charts.length === 1;
},
embedClass() {
return this.isSingleChart ? this.containerClass : 'col-lg-12';
},
panelClass() {
return this.isSingleChart ? 'col-lg-12' : 'col-lg-6';
},
},
mounted() {
this.setInitialState();
......@@ -60,15 +81,27 @@ export default {
}
},
methods: {
...mapActions('monitoringDashboard', [
'setTimeRange',
'fetchDashboard',
'setEndpoints',
'setFeatureFlags',
'setShowErrorBanner',
]),
// Use function args to support dynamic namespaces in mapXXX helpers. Pattern described
// in https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
...mapActions({
setTimeRange(dispatch, payload) {
return dispatch(`${this.namespace}/setTimeRange`, payload);
},
fetchDashboard(dispatch, payload) {
return dispatch(`${this.namespace}/fetchDashboard`, payload);
},
setEndpoints(dispatch, payload) {
return dispatch(`${this.namespace}/setEndpoints`, payload);
},
setFeatureFlags(dispatch, payload) {
return dispatch(`${this.namespace}/setFeatureFlags`, payload);
},
setShowErrorBanner(dispatch, payload) {
return dispatch(`${this.namespace}/setShowErrorBanner`, payload);
},
}),
chartHasData(chart) {
return chart.metrics.some(metric => this.metricsWithData().includes(metric.metricId));
return chart.metrics.some(metric => this.metricsWithData.includes(metric.metricId));
},
onSidebarMutation() {
setTimeout(() => {
......@@ -85,15 +118,14 @@ export default {
};
</script>
<template>
<div class="metrics-embed" :class="{ 'd-inline-flex col-lg-6 p-0': isSingleChart }">
<div v-if="charts.length" class="row w-100 m-n2 pb-4">
<panel-type
v-for="(graphData, graphIndex) in charts"
:key="`panel-type-${graphIndex}`"
class="w-100"
:graph-data="graphData"
:group-id="dashboardUrl"
/>
</div>
<div class="metrics-embed p-0 d-flex flex-wrap" :class="embedClass">
<panel-type
v-for="(graphData, graphIndex) in charts"
:key="`panel-type-${graphIndex}`"
:class="panelClass"
:graph-data="graphData"
:group-id="dashboardUrl"
:namespace="namespace"
/>
</div>
</template>
......@@ -68,6 +68,11 @@ export default {
required: false,
default: 'panel-type-chart',
},
namespace: {
type: String,
required: false,
default: 'monitoringDashboard',
},
},
data() {
return {
......@@ -76,7 +81,22 @@ export default {
};
},
computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
// Use functions to support dynamic namespaces in mapXXX helpers. Pattern described
// in https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
...mapState({
deploymentData(state) {
return state[this.namespace].deploymentData;
},
projectPath(state) {
return state[this.namespace].projectPath;
},
logsPath(state) {
return state[this.namespace].logsPath;
},
timeRange(state) {
return state[this.namespace].timeRange;
},
}),
title() {
return this.graphData.title || '';
},
......
import * as types from './mutation_types';
export const addModule = ({ commit }, data) => commit(types.ADD_MODULE, data);
export default () => {};
export const metricsWithData = (state, getters, rootState, rootGetters) =>
state.modules.map(module => rootGetters[`${module}/metricsWithData`]().length);
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
// In practice this store will have a number of `monitoringDashboard` modules added dynamically
export const createStore = () =>
new Vuex.Store({
modules: {
embedGroup: {
namespaced: true,
actions,
getters,
mutations,
state,
},
},
});
export default createStore();
export const ADD_MODULE = 'ADD_MODULE';
export default () => {};
import * as types from './mutation_types';
export default {
[types.ADD_MODULE](state, module) {
state.modules.push(module);
},
};
......@@ -7,16 +7,18 @@ import state from './state';
Vue.use(Vuex);
export const monitoringDashboard = {
namespaced: true,
actions,
getters,
mutations,
state,
};
export const createStore = () =>
new Vuex.Store({
modules: {
monitoringDashboard: {
namespaced: true,
actions,
getters,
mutations,
state,
},
monitoringDashboard,
},
});
......
.collapsible-card {
.collapsible-card-btn {
color: $gl-text-color;
&:hover {
color: $blue-600;
}
}
}
......@@ -60,4 +60,6 @@
.settings-content
= render 'usage'
= render_if_exists 'admin/application_settings/seat_link_setting', expanded: expanded_by_default?
= render_if_exists 'admin/application_settings/pseudonymizer_settings', expanded: expanded_by_default?
---
title: Optimize service desk enabled projects counter
merge_request: 27589
author:
type: performance
---
title: Add file-based pipeline conditions to default Auto DevOps CI template
merge_request: 28242
author:
type: changed
---
title: Allow embedded metrics charts to be hidden
merge_request: 23929
author:
type: added
......@@ -210,6 +210,7 @@ Gitlab.ee do
Settings.gitlab['mirror_max_delay'] ||= 300
Settings.gitlab['mirror_max_capacity'] ||= 30
Settings.gitlab['mirror_capacity_threshold'] ||= 15
Settings.gitlab['seat_link_enabled'] = true if Settings.gitlab['seat_link_enabled'].nil?
end
#
......
# frozen_string_literal: true
class AddIndexOnIdCreatorIdAndCreatedAtToProjectsTable < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'index_service_desk_enabled_projects_on_id_creator_id_created_at'
disable_ddl_transaction!
def up
add_concurrent_index :projects, [:id, :creator_id, :created_at], where: '"projects"."service_desk_enabled" = TRUE', name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :projects, INDEX_NAME
end
end
# frozen_string_literal: true
class AddSeatLinkEnabledToApplicationSettings < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:application_settings, :seat_link_enabled,
:boolean,
default: true,
allow_null: false)
end
def down
remove_column(:application_settings, :seat_link_enabled)
end
end
......@@ -396,7 +396,8 @@ CREATE TABLE public.application_settings (
email_restrictions_enabled boolean DEFAULT false NOT NULL,
email_restrictions text,
npm_package_requests_forwarding boolean DEFAULT true NOT NULL,
namespace_storage_size_limit bigint DEFAULT 0 NOT NULL
namespace_storage_size_limit bigint DEFAULT 0 NOT NULL,
seat_link_enabled boolean DEFAULT true NOT NULL
);
CREATE SEQUENCE public.application_settings_id_seq
......@@ -9924,6 +9925,8 @@ CREATE INDEX index_serverless_domain_cluster_on_creator_id ON public.serverless_
CREATE INDEX index_serverless_domain_cluster_on_pages_domain_id ON public.serverless_domain_cluster USING btree (pages_domain_id);
CREATE INDEX index_service_desk_enabled_projects_on_id_creator_id_created_at ON public.projects USING btree (id, creator_id, created_at) WHERE (service_desk_enabled = true);
CREATE INDEX index_services_on_project_id_and_type ON public.services USING btree (project_id, type);
CREATE INDEX index_services_on_template ON public.services USING btree (template);
......@@ -12846,7 +12849,9 @@ COPY "schema_migrations" (version) FROM STDIN;
20200323080714
20200323122201
20200323134519
20200324093258
20200324115359
20200325152327
20200325160952
20200325183636
\.
......
......@@ -646,6 +646,13 @@ NOTE: **Note:**
The garbage collection tools are only available when you've installed GitLab
via an Omnibus package or the cloud native chart.
DANGER: **Danger:**
By running the built-in garbage collection command, it will cause downtime to
the Container Registry. Running this command on an instance in an HA environment
while one of your other instances is still writing to the Registry storage,
will remove referenced manifests. To avoid that, make sure Registry is set to
[read-only mode](#performing-garbage-collection-without-downtime) before proceeding.
Container Registry can use considerable amounts of disk space. To clear up
some unused layers, the registry includes a garbage collect command.
......@@ -695,13 +702,6 @@ built-in command:
specify its path.
- After the garbage collection is done, the registry should start up automatically.
DANGER: **Danger:**
By running the built-in garbage collection command, it will cause downtime to
the Container Registry. Running this command on an instance in an HA environment
while one of your other instances is still writing to the Registry storage,
will remove referenced manifests. To avoid that, make sure Registry is set to
[read-only mode](#performing-garbage-collection-without-downtime) before proceeding.
If you did not change the default location of the configuration file, run:
```sh
......
......@@ -135,16 +135,6 @@ Instances deployed in our private subnets need to connect to the internet for up
Create a second NAT gateway but this time place it in the second public subnet, `gitlab-public-10.0.2.0`.
### Route Table
Up to now all our subnets are private. We need to create a Route Table
to associate an Internet Gateway. On the same VPC dashboard:
1. Select **Route Tables** from the left menu.
1. Click **Create Route Table**.
1. At the "Name tag" enter `gitlab-public` and choose `gitlab-vpc` under "VPC".
1. Hit **Yes, Create**.
### Internet Gateway
Now, still on the same dashboard, go to Internet Gateways and
......@@ -160,25 +150,44 @@ create a new one:
1. Choose `gitlab-vpc` from the list and hit **Attach**.
### Configuring subnets
### Route Tables
#### Public Route Table
We need to create a route table for our public subnets to reach the internet via the internet gateway we created in the previous step.
We now need to add a new target which will be our Internet Gateway and have
On the VPC dashboard:
1. Select **Route Tables** from the left menu.
1. Click **Create Route Table**.
1. At the "Name tag" enter `gitlab-public` and choose `gitlab-vpc` under "VPC".
1. Click **Create**.
We now need to add our internet gateway as a new target and have
it receive traffic from any destination.
1. Select **Route Tables** from the left menu and select the `gitlab-public`
route to show the options at the bottom.
1. Select the **Routes** tab, hit **Edit > Add another route** and set `0.0.0.0/0`
as destination. In the target, select the `gitlab-gateway` we created previously.
Hit **Save** once done.
![Associate subnet with gateway](img/associate_subnet_gateway.png)
1. Select the **Routes** tab, click **Edit routes > Add route** and set `0.0.0.0/0`
as the destination. In the target column, select the `gitlab-gateway` we created previously.
Hit **Save routes** once done.
Next, we must associate the **public** subnets to the route table:
1. Select the **Subnet Associations** tab and hit **Edit**.
1. Check only the public subnet and hit **Save**.
1. Select the **Subnet Associations** tab and click **Edit subnet associations**.
1. Check only the public subnets and click **Save**.
#### Private Route Tables
We also need to create two private route tables so that instances in each private subnet can reach the internet via the NAT gateway in the corresponding public subnet in the same availability zone.
![Associate subnet with gateway](img/associate_subnet_gateway_2.png)
1. Follow the same steps as above to create two private route tables. Name them `gitlab-public-a` and `gitlab-public-b` respectively.
1. Next, add a new route to each of the private route tables where the destination is `0.0.0.0/0` and the target is one of the NAT gateways we created earlier.
1. Add the NAT gateway we created in `gitlab-public-10.0.0.0` as the target for the new route in the `gitlab-public-a` route table.
1. Similarly, add the NAT gateway in `gitlab-public-10.0.2.0` as the target for the new route in the `gitlab-public-b`.
1. Lastly, associate each private subnet with a private route table.
1. Associate `gitlab-private-10.0.1.0` with `gitlab-public-a`.
1. Associate `gitlab-private-10.0.3.0` with `gitlab-public-b`.
---
......
......@@ -245,11 +245,12 @@ Seat Link allows us to provide our self-managed customers with prorated charges
Seat Link sends to GitLab daily a count of all users in connected self-managed instances. That information is used to automate prorated reconciliations. The data is sent securely through an encrypted HTTPS connection.
Seat Link is mandatory because we need the user count data to enable prorated billing. Seat Link provides **only** the following information to GitLab:
Seat Link provides **only** the following information to GitLab:
- Date
- License key
- Historical maximum user count
- Active users count
For air-gapped or closed network customers, the existing [true-up model](#users-over-license) will be used. Prorated charges are not possible without user count data.
......@@ -293,12 +294,39 @@ TjJ4eVlVUkdkWEJtDQpkSHByYWpreVJrcG9UVlo0Y0hKSU9URndiV2RzVFdO
VlhHNXRhVmszTkV0SVEzcEpNMWRyZEVoRU4ydHINCmRIRnFRVTlCVUVVM1pV
SlRORE4xUjFaYVJGb3JlWGM5UFZ4dUlpd2lhWFlpt2lKV00yRnNVbk5RTjJk
Sg0KU1hNMGExaE9SVGR2V2pKQlBUMWNiaUo5DQo=',
max_historical_user_count: 10
max_historical_user_count: 10,
active_users: 6
}
</code></pre>
</details>
#### Disable Seat Link
Seat Link is enabled by default. To disable this feature, go to
**{admin}** **Admin Area > Settings > Metrics and profiling** and
clear the Seat Link checkbox.
To disable Seat Link in an Omnibus GitLab installation, and prevent it from
being configured in the future through the administration panel, set the following in
[`gitlab.rb`](https://docs.gitlab.com/omnibus/settings/configuration.html#configuration-options):
```ruby
gitlab_rails['seat_link_enabled'] = false
```
To disable Seat Link in a GitLab source installation, and prevent it from
being configured in the future through the administration panel,
set the following in `gitlab.yml`:
```yaml
production: &base
# ...
gitlab:
# ...
seat_link_enabled: false
```
### Renew or change a GitLab.com subscription
To renew for more users than are currently active in your GitLab.com system, contact our sales team via `renewals@gitlab.com` for assistance as this can't be done in the Customers Portal.
......
......@@ -777,7 +777,11 @@ The following requirements must be met for the metric to unfurl:
If all of the above are true, then the metric will unfurl as seen below:
![Embedded Metrics](img/embed_metrics.png)
![Embedded Metrics](img/view_embedded_metrics_v12_10.png)
Metric charts may also be hidden:
![Show Hide](img/hide_embedded_metrics_v12_10.png)
### Embedding metrics in issue templates
......
......@@ -23,15 +23,7 @@ module Gitlab
private
def template_name
if beta_enabled?
'Beta/Auto-DevOps'
else
'Auto-DevOps'
end
end
def beta_enabled?
Feature.enabled?(:auto_devops_beta, project, default_enabled: true)
'Auto-DevOps'
end
end
end
......
......@@ -23,15 +23,7 @@ module Gitlab
private
def template_name
if beta_enabled?
'Beta/Auto-DevOps'
else
'Auto-DevOps'
end
end
def beta_enabled?
Feature.enabled?(:auto_devops_beta, project, default_enabled: true)
'Auto-DevOps'
end
end
end
......
......@@ -72,6 +72,83 @@ stages:
- performance
- cleanup
workflow:
rules:
- if: '$BUILDPACK_URL || $AUTO_DEVOPS_EXPLICITLY_ENABLED == "1"'
- exists:
- Dockerfile
# https://github.com/heroku/heroku-buildpack-clojure
- exists:
- project.clj
# https://github.com/heroku/heroku-buildpack-go
- exists:
- go.mod
- Gopkg.mod
- Godeps/Godeps.json
- vendor/vendor.json
- glide.yaml
- src/**/*.go
# https://github.com/heroku/heroku-buildpack-gradle
- exists:
- gradlew
- build.gradle
- settings.gradle
# https://github.com/heroku/heroku-buildpack-java
- exists:
- pom.xml
- pom.atom
- pom.clj
- pom.groovy
- pom.rb
- pom.scala
- pom.yaml
- pom.yml
# https://github.com/heroku/heroku-buildpack-multi
- exists:
- .buildpacks
# https://github.com/heroku/heroku-buildpack-nodejs
- exists:
- package.json
# https://github.com/heroku/heroku-buildpack-php
- exists:
- composer.json
- index.php
# https://github.com/heroku/heroku-buildpack-play
# TODO: detect script excludes some scala files
- exists:
- '**/conf/application.conf'
# https://github.com/heroku/heroku-buildpack-python
# TODO: detect script checks that all of these exist, not any
- exists:
- requirements.txt
- setup.py
- Pipfile
# https://github.com/heroku/heroku-buildpack-ruby
- exists:
- Gemfile
# https://github.com/heroku/heroku-buildpack-scala
- exists:
- '*.sbt'
- project/*.scala
- .sbt/*.scala
- project/build.properties
# https://github.com/dokku/buildpack-nginx
- exists:
- .static
include:
- template: Jobs/Build.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
- template: Jobs/Test.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml
......
# Auto DevOps - BETA do not use
# This CI/CD configuration provides a standard pipeline for
# * building a Docker image (using a buildpack if necessary),
# * storing the image in the container registry,
# * running tests from a buildpack,
# * running code quality analysis,
# * creating a review app for each topic branch,
# * and continuous deployment to production
#
# Test jobs may be disabled by setting environment variables:
# * test: TEST_DISABLED
# * code_quality: CODE_QUALITY_DISABLED
# * license_management: LICENSE_MANAGEMENT_DISABLED
# * performance: PERFORMANCE_DISABLED
# * sast: SAST_DISABLED
# * dependency_scanning: DEPENDENCY_SCANNING_DISABLED
# * container_scanning: CONTAINER_SCANNING_DISABLED
# * dast: DAST_DISABLED
# * review: REVIEW_DISABLED
# * stop_review: REVIEW_DISABLED
#
# In order to deploy, you must have a Kubernetes cluster configured either
# via a project integration, or via group/project variables.
# KUBE_INGRESS_BASE_DOMAIN must also be set on the cluster settings,
# as a variable at the group or project level, or manually added below.
#
# Continuous deployment to production is enabled by default.
# If you want to deploy to staging first, set STAGING_ENABLED environment variable.
# If you want to enable incremental rollout, either manual or time based,
# set INCREMENTAL_ROLLOUT_MODE environment variable to "manual" or "timed".
# If you want to use canary deployments, set CANARY_ENABLED environment variable.
#
# If Auto DevOps fails to detect the proper buildpack, or if you want to
# specify a custom buildpack, set a project variable `BUILDPACK_URL` to the
# repository URL of the buildpack.
# e.g. BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-ruby.git#v142
# If you need multiple buildpacks, add a file to your project called
# `.buildpacks` that contains the URLs, one on each line, in order.
# Note: Auto CI does not work with multiple buildpacks yet
image: alpine:latest
variables:
# KUBE_INGRESS_BASE_DOMAIN is the application deployment domain and should be set as a variable at the group or project level.
# KUBE_INGRESS_BASE_DOMAIN: domain.example.com
POSTGRES_USER: user
POSTGRES_PASSWORD: testing-password
POSTGRES_ENABLED: "true"
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
POSTGRES_VERSION: 9.6.2
DOCKER_DRIVER: overlay2
ROLLOUT_RESOURCE_TYPE: deployment
DOCKER_TLS_CERTDIR: "" # https://gitlab.com/gitlab-org/gitlab-runner/issues/4501
stages:
- build
- test
- deploy # dummy stage to follow the template guidelines
- review
- dast
- staging
- canary
- production
- incremental rollout 10%
- incremental rollout 25%
- incremental rollout 50%
- incremental rollout 100%
- performance
- cleanup
workflow:
rules:
- if: '$BUILDPACK_URL || $AUTO_DEVOPS_EXPLICITLY_ENABLED == "1"'
- exists:
- Dockerfile
# https://github.com/heroku/heroku-buildpack-clojure
- exists:
- project.clj
# https://github.com/heroku/heroku-buildpack-go
- exists:
- go.mod
- Gopkg.mod
- Godeps/Godeps.json
- vendor/vendor.json
- glide.yaml
- src/**/*.go
# https://github.com/heroku/heroku-buildpack-gradle
- exists:
- gradlew
- build.gradle
- settings.gradle
# https://github.com/heroku/heroku-buildpack-java
- exists:
- pom.xml
- pom.atom
- pom.clj
- pom.groovy
- pom.rb
- pom.scala
- pom.yaml
- pom.yml
# https://github.com/heroku/heroku-buildpack-multi
- exists:
- .buildpacks
# https://github.com/heroku/heroku-buildpack-nodejs
- exists:
- package.json
# https://github.com/heroku/heroku-buildpack-php
- exists:
- composer.json
- index.php
# https://github.com/heroku/heroku-buildpack-play
# TODO: detect script excludes some scala files
- exists:
- '**/conf/application.conf'
# https://github.com/heroku/heroku-buildpack-python
# TODO: detect script checks that all of these exist, not any
- exists:
- requirements.txt
- setup.py
- Pipfile
# https://github.com/heroku/heroku-buildpack-ruby
- exists:
- Gemfile
# https://github.com/heroku/heroku-buildpack-scala
- exists:
- '*.sbt'
- project/*.scala
- .sbt/*.scala
- project/build.properties
# https://github.com/dokku/buildpack-nginx
- exists:
- .static
include:
- template: Jobs/Build.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
- template: Jobs/Test.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Test.gitlab-ci.yml
- template: Jobs/Code-Quality.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
- template: Jobs/Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
- template: Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
- template: Jobs/Browser-Performance-Testing.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
- template: Security/DAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/License-Management.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
......@@ -4,12 +4,18 @@
# Implements a distinct and ordinary batch counter
# Needs indexes on the column below to calculate max, min and range queries
# For larger tables just set use higher batch_size with index optimization
#
# In order to not use a possible complex time consuming query when calculating min and max for batch_distinct_count
# the start and finish can be sent specifically
#
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22705
#
# Examples:
# extend ::Gitlab::Database::BatchCount
# batch_count(User.active)
# batch_count(::Clusters::Cluster.aws_installed.enabled, :cluster_id)
# batch_distinct_count(::Project, :creator_id)
# batch_distinct_count(::Project.with_active_services.service_desk_enabled.where(time_period), start: ::User.minimum(:id), finish: ::User.maximum(:id))
module Gitlab
module Database
module BatchCount
......@@ -17,8 +23,8 @@ module Gitlab
BatchCounter.new(relation, column: column).count(batch_size: batch_size)
end
def batch_distinct_count(relation, column = nil, batch_size: nil)
BatchCounter.new(relation, column: column).count(mode: :distinct, batch_size: batch_size)
def batch_distinct_count(relation, column = nil, batch_size: nil, start: nil, finish: nil)
BatchCounter.new(relation, column: column).count(mode: :distinct, batch_size: batch_size, start: start, finish: finish)
end
class << self
......@@ -31,9 +37,10 @@ module Gitlab
MIN_REQUIRED_BATCH_SIZE = 1_250
MAX_ALLOWED_LOOPS = 10_000
SLEEP_TIME_IN_SECONDS = 0.01 # 10 msec sleep
# Each query should take <<500ms https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22705
DEFAULT_DISTINCT_BATCH_SIZE = 10_000
DEFAULT_BATCH_SIZE = 100_000
# Each query should take < 500ms https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22705
DEFAULT_DISTINCT_BATCH_SIZE = 100_000
DEFAULT_BATCH_SIZE = 10_000
def initialize(relation, column: nil)
@relation = relation
......@@ -46,15 +53,15 @@ module Gitlab
start > finish
end
def count(batch_size: nil, mode: :itself)
def count(batch_size: nil, mode: :itself, start: nil, finish: nil)
raise 'BatchCount can not be run inside a transaction' if ActiveRecord::Base.connection.transaction_open?
raise "The mode #{mode.inspect} is not supported" unless [:itself, :distinct].include?(mode)
# non-distinct have better performance
batch_size ||= mode == :distinct ? DEFAULT_DISTINCT_BATCH_SIZE : DEFAULT_BATCH_SIZE
start = @relation.minimum(@column) || 0
finish = @relation.maximum(@column) || 0
start = actual_start(start)
finish = actual_finish(finish)
raise "Batch counting expects positive values only for #{@column}" if start < 0 || finish < 0
return FALLBACK if unwanted_configuration?(finish, batch_size, start)
......@@ -84,6 +91,16 @@ module Gitlab
# rubocop:disable GitlabSecurity/PublicSend
@relation.select(@column).public_send(mode).where(@column => start..(finish - 1)).count
end
private
def actual_start(start)
start || @relation.minimum(@column) || 0
end
def actual_finish(finish)
finish || @relation.maximum(@column) || 0
end
end
end
end
......@@ -240,9 +240,9 @@ module Gitlab
fallback
end
def distinct_count(relation, column = nil, fallback: -1, batch: true)
def distinct_count(relation, column = nil, fallback: -1, batch: true, start: nil, finish: nil)
if batch && Feature.enabled?(:usage_ping_batch_counter, default_enabled: true)
Gitlab::Database::BatchCount.batch_distinct_count(relation, column)
Gitlab::Database::BatchCount.batch_distinct_count(relation, column, start: start, finish: finish)
else
relation.distinct_count_by(column)
end
......
......@@ -339,6 +339,9 @@ msgstr ""
msgid "%{lineOneStart}Drag and drop to upload your designs%{lineOneEnd} or %{linkStart}click to upload%{linkEnd}."
msgstr ""
msgid "%{link_start}Learn more%{link_end} about what information is shared with GitLab Inc."
msgstr ""
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
......@@ -7458,6 +7461,9 @@ msgstr ""
msgid "Enable SAML authentication for this group"
msgstr ""
msgid "Enable Seat Link"
msgstr ""
msgid "Enable access to Grafana"
msgstr ""
......@@ -7500,6 +7506,9 @@ msgstr ""
msgid "Enable mirror configuration"
msgstr ""
msgid "Enable or disable Seat Link."
msgstr ""
msgid "Enable or disable keyboard shortcuts"
msgstr ""
......@@ -9012,6 +9021,9 @@ msgstr ""
msgid "For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}."
msgstr ""
msgid "For more information, see the documentation on %{link_start}disabling Seat Link%{link_end}."
msgstr ""
msgid "For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)"
msgstr ""
......@@ -10488,6 +10500,11 @@ msgstr ""
msgid "Hide archived projects"
msgstr ""
msgid "Hide chart"
msgid_plural "Hide charts"
msgstr[0] ""
msgstr[1] ""
msgid "Hide file browser"
msgstr ""
......@@ -17543,6 +17560,12 @@ msgid_plural "SearchResults|wiki results"
msgstr[0] ""
msgstr[1] ""
msgid "Seat Link"
msgstr ""
msgid "Seat Link is disabled, and cannot be configured through this form."
msgstr ""
msgid "Seats currently in use"
msgstr ""
......@@ -21101,6 +21124,9 @@ msgstr ""
msgid "To set up this service:"
msgstr ""
msgid "To simplify the billing process, GitLab will collect user counts in order to prorate charges for user growth throughout the year using a quarterly reconciliation process."
msgstr ""
msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there."
msgstr ""
......@@ -22340,6 +22366,11 @@ msgstr ""
msgid "View blame prior to this change"
msgstr ""
msgid "View chart"
msgid_plural "View charts"
msgstr[0] ""
msgstr[1] ""
msgid "View dependency details for your project"
msgstr ""
......
import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
import renderMetrics from '~/behaviors/markdown/render_metrics';
const originalExtend = Vue.extend;
const mockEmbedGroup = jest.fn();
describe('Render metrics for Gitlab Flavoured Markdown', () => {
const container = {
Metrics() {},
};
let spyExtend;
beforeEach(() => {
Vue.extend = () => container.Metrics;
spyExtend = jest.spyOn(Vue, 'extend');
});
jest.mock('vue', () => ({ extend: () => mockEmbedGroup }));
jest.mock('~/monitoring/components/embeds/embed_group.vue', () => jest.fn());
jest.mock('~/monitoring/stores/embed_group/', () => ({ createStore: jest.fn() }));
afterEach(() => {
Vue.extend = originalExtend;
});
const getElements = () => Array.from(document.getElementsByClassName('js-render-metrics'));
describe('Render metrics for Gitlab Flavoured Markdown', () => {
it('does nothing when no elements are found', () => {
renderMetrics([]);
expect(spyExtend).not.toHaveBeenCalled();
expect(mockEmbedGroup).not.toHaveBeenCalled();
});
it('renders a vue component when elements are found', () => {
const element = document.createElement('div');
element.setAttribute('data-dashboard-url', TEST_HOST);
document.body.innerHTML = `<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}"></div>`;
renderMetrics([element]);
renderMetrics(getElements());
expect(mockEmbedGroup).toHaveBeenCalledTimes(1);
expect(mockEmbedGroup).toHaveBeenCalledWith(
expect.objectContaining({ propsData: { urls: [`${TEST_HOST}`] } }),
);
});
expect(spyExtend).toHaveBeenCalled();
it('takes sibling metrics and groups them under a shared parent', () => {
document.body.innerHTML = `
<p><span>Hello</span></p>
<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}/1"></div>
<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}/2"></div>
<p><span>Hello</span></p>
<div class="js-render-metrics" data-dashboard-url="${TEST_HOST}/3"></div>
`;
renderMetrics(getElements());
expect(mockEmbedGroup).toHaveBeenCalledTimes(2);
expect(mockEmbedGroup).toHaveBeenCalledWith(
expect.objectContaining({ propsData: { urls: [`${TEST_HOST}/1`, `${TEST_HOST}/2`] } }),
);
expect(mockEmbedGroup).toHaveBeenCalledWith(
expect.objectContaining({ propsData: { urls: [`${TEST_HOST}/3`] } }),
);
});
});
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlButton, GlCard } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import EmbedGroup from '~/monitoring/components/embeds/embed_group.vue';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import {
addModuleAction,
initialEmbedGroupState,
singleEmbedProps,
dashboardEmbedProps,
multipleEmbedProps,
} from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Embed Group', () => {
let wrapper;
let store;
const metricsWithDataGetter = jest.fn();
function mountComponent({ urls = [TEST_HOST], shallow = true, stubs } = {}) {
const mountMethod = shallow ? shallowMount : mount;
wrapper = mountMethod(EmbedGroup, {
localVue,
store,
propsData: {
urls,
},
stubs,
});
}
beforeEach(() => {
store = new Vuex.Store({
modules: {
embedGroup: {
namespaced: true,
actions: { addModule: jest.fn() },
getters: { metricsWithData: metricsWithDataGetter },
state: initialEmbedGroupState,
},
},
});
store.registerModule = jest.fn();
jest.spyOn(store, 'dispatch');
});
afterEach(() => {
metricsWithDataGetter.mockReset();
if (wrapper) {
wrapper.destroy();
}
});
describe('interactivity', () => {
it('hides the component when no chart data is loaded', () => {
metricsWithDataGetter.mockReturnValue([]);
mountComponent();
expect(wrapper.find(GlCard).isVisible()).toBe(false);
});
it('shows the component when chart data is loaded', () => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent();
expect(wrapper.find(GlCard).isVisible()).toBe(true);
});
it('is expanded by default', () => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
expect(wrapper.find('.card-body').classes()).not.toContain('d-none');
});
it('collapses when clicked', done => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
wrapper.find(GlButton).trigger('click');
wrapper.vm.$nextTick(() => {
expect(wrapper.find('.card-body').classes()).toContain('d-none');
done();
});
});
});
describe('single metrics', () => {
beforeEach(() => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent();
});
it('renders an Embed component', () => {
expect(wrapper.find(MetricEmbed).exists()).toBe(true);
});
it('passes the correct props to the Embed component', () => {
expect(wrapper.find(MetricEmbed).props()).toEqual(singleEmbedProps());
});
it('adds the monitoring dashboard module', () => {
expect(store.dispatch).toHaveBeenCalledWith(addModuleAction, 'monitoringDashboard/0');
});
});
describe('dashboard metrics', () => {
beforeEach(() => {
metricsWithDataGetter.mockReturnValue([2]);
mountComponent();
});
it('passes the correct props to the dashboard Embed component', () => {
expect(wrapper.find(MetricEmbed).props()).toEqual(dashboardEmbedProps());
});
it('adds the monitoring dashboard module', () => {
expect(store.dispatch).toHaveBeenCalledWith(addModuleAction, 'monitoringDashboard/0');
});
});
describe('multiple metrics', () => {
beforeEach(() => {
metricsWithDataGetter.mockReturnValue([1, 1]);
mountComponent({ urls: [TEST_HOST, TEST_HOST] });
});
it('creates Embed components', () => {
expect(wrapper.findAll(MetricEmbed)).toHaveLength(2);
});
it('passes the correct props to the Embed components', () => {
expect(wrapper.findAll(MetricEmbed).wrappers.map(item => item.props())).toEqual(
multipleEmbedProps(),
);
});
it('adds multiple monitoring dashboard modules', () => {
expect(store.dispatch).toHaveBeenCalledWith(addModuleAction, 'monitoringDashboard/0');
expect(store.dispatch).toHaveBeenCalledWith(addModuleAction, 'monitoringDashboard/1');
});
});
describe('button text', () => {
it('has a singular label when there is one embed', () => {
metricsWithDataGetter.mockReturnValue([1]);
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
expect(wrapper.find(GlButton).text()).toBe('Hide chart');
});
it('has a plural label when there are multiple embeds', () => {
metricsWithDataGetter.mockReturnValue([2]);
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
expect(wrapper.find(GlButton).text()).toBe('Hide charts');
});
});
});
......@@ -2,20 +2,20 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
import { TEST_HOST } from 'helpers/test_constants';
import Embed from '~/monitoring/components/embed.vue';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import { groups, initialState, metricsData, metricsWithData } from './mock_data';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Embed', () => {
describe('MetricEmbed', () => {
let wrapper;
let store;
let actions;
let metricsWithDataGetter;
function mountComponent() {
wrapper = shallowMount(Embed, {
wrapper = shallowMount(MetricEmbed, {
localVue,
store,
propsData: {
......
import { TEST_HOST } from 'helpers/test_constants';
export const metricsWithData = ['15_metric_a', '16_metric_b'];
export const groups = [
......@@ -52,3 +54,34 @@ export const initialState = () => ({
},
useDashboardEndpoint: true,
});
export const initialEmbedGroupState = () => ({
modules: [],
});
export const singleEmbedProps = () => ({
dashboardUrl: TEST_HOST,
containerClass: 'col-lg-12',
namespace: 'monitoringDashboard/0',
});
export const dashboardEmbedProps = () => ({
dashboardUrl: TEST_HOST,
containerClass: 'col-lg-6',
namespace: 'monitoringDashboard/0',
});
export const multipleEmbedProps = () => [
{
dashboardUrl: TEST_HOST,
containerClass: 'col-lg-6',
namespace: 'monitoringDashboard/0',
},
{
dashboardUrl: TEST_HOST,
containerClass: 'col-lg-6',
namespace: 'monitoringDashboard/1',
},
];
export const addModuleAction = 'embedGroup/addModule';
......@@ -8,8 +8,17 @@ import PanelType from '~/monitoring/components/panel_type.vue';
import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
import TimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import AnomalyChart from '~/monitoring/components/charts/anomaly.vue';
import { anomalyMockGraphData, graphDataPrometheusQueryRange } from 'jest/monitoring/mock_data';
import { createStore } from '~/monitoring/stores';
import {
anomalyMockGraphData,
graphDataPrometheusQueryRange,
mockLogsHref,
mockLogsPath,
mockNamespace,
mockNamespacedData,
mockTimeRange,
} from 'jest/monitoring/mock_data';
import { createStore, monitoringDashboard } from '~/monitoring/stores';
import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group';
global.IS_EE = true;
global.URL.createObjectURL = jest.fn();
......@@ -29,6 +38,7 @@ describe('Panel Type component', () => {
const exampleText = 'example_text';
const findCopyLink = () => wrapper.find({ ref: 'copyChartLink' });
const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
const createWrapper = props => {
wrapper = shallowMount(PanelType, {
......@@ -99,8 +109,6 @@ describe('Panel Type component', () => {
});
describe('when graph data is available', () => {
const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
beforeEach(() => {
createWrapper({
graphData: graphDataPrometheusQueryRange,
......@@ -242,10 +250,6 @@ describe('Panel Type component', () => {
});
describe('View Logs dropdown item', () => {
const mockLogsPath = '/path/to/logs';
const mockTimeRange = { duration: { seconds: 120 } };
const findTimeChart = () => wrapper.find({ ref: 'timeChart' });
const findViewLogsLink = () => wrapper.find({ ref: 'viewLogsLink' });
beforeEach(() => {
......@@ -292,8 +296,7 @@ describe('Panel Type component', () => {
state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => {
const href = `${mockLogsPath}?duration_seconds=${mockTimeRange.duration.seconds}`;
expect(findViewLogsLink().attributes('href')).toMatch(href);
expect(findViewLogsLink().attributes('href')).toMatch(mockLogsHref);
});
});
......@@ -388,4 +391,53 @@ describe('Panel Type component', () => {
});
});
});
describe('when using dynamic modules', () => {
const { mockDeploymentData, mockProjectPath } = mockNamespacedData;
beforeEach(() => {
store = createEmbedGroupStore();
store.registerModule(mockNamespace, monitoringDashboard);
store.state.embedGroup.modules.push(mockNamespace);
wrapper = shallowMount(PanelType, {
propsData: {
graphData: graphDataPrometheusQueryRange,
namespace: mockNamespace,
},
store,
mocks,
});
});
it('handles namespaced time range and logs path state', () => {
store.state[mockNamespace].timeRange = mockTimeRange;
store.state[mockNamespace].logsPath = mockLogsPath;
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find({ ref: 'viewLogsLink' }).attributes().href).toBe(mockLogsHref);
});
});
it('handles namespaced deployment data state', () => {
store.state[mockNamespace].deploymentData = mockDeploymentData;
return wrapper.vm.$nextTick().then(() => {
expect(findTimeChart().props().deploymentData).toEqual(mockDeploymentData);
});
});
it('handles namespaced project path state', () => {
store.state[mockNamespace].projectPath = mockProjectPath;
return wrapper.vm.$nextTick().then(() => {
expect(findTimeChart().props().projectPath).toBe(mockProjectPath);
});
});
it('it renders a time series chart with no errors', () => {
expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true);
expect(wrapper.find(TimeSeriesChart).exists()).toBe(true);
});
});
});
......@@ -750,3 +750,20 @@ export const barMockData = {
},
],
};
export const baseNamespace = 'monitoringDashboard';
export const mockNamespace = `${baseNamespace}/1`;
export const mockNamespaces = [`${baseNamespace}/1`, `${baseNamespace}/2`];
export const mockTimeRange = { duration: { seconds: 120 } };
export const mockNamespacedData = {
mockDeploymentData: ['mockDeploymentData'],
mockProjectPath: '/mockProjectPath',
};
export const mockLogsPath = '/mockLogsPath';
export const mockLogsHref = `${mockLogsPath}?duration_seconds=${mockTimeRange.duration.seconds}`;
// import store from '~/monitoring/stores/embed_group';
import * as actions from '~/monitoring/stores/embed_group/actions';
import * as types from '~/monitoring/stores/embed_group/mutation_types';
import { mockNamespace } from '../../mock_data';
describe('Embed group actions', () => {
describe('addModule', () => {
it('adds a module to the store', () => {
const commit = jest.fn();
actions.addModule({ commit }, mockNamespace);
expect(commit).toHaveBeenCalledWith(types.ADD_MODULE, mockNamespace);
});
});
});
import { metricsWithData } from '~/monitoring/stores/embed_group/getters';
import { mockNamespaces } from '../../mock_data';
describe('Embed group getters', () => {
describe('metricsWithData', () => {
it('correctly sums the number of metrics with data', () => {
const mockMetric = {};
const state = {
modules: mockNamespaces,
};
const rootGetters = {
[`${mockNamespaces[0]}/metricsWithData`]: () => [mockMetric],
[`${mockNamespaces[1]}/metricsWithData`]: () => [mockMetric, mockMetric],
};
expect(metricsWithData(state, null, null, rootGetters)).toEqual([1, 2]);
});
});
});
import state from '~/monitoring/stores/embed_group/state';
import mutations from '~/monitoring/stores/embed_group/mutations';
import * as types from '~/monitoring/stores/embed_group/mutation_types';
import { mockNamespace } from '../../mock_data';
describe('Embed group mutations', () => {
describe('ADD_MODULE', () => {
it('should add a module', () => {
const stateCopy = state();
mutations[types.ADD_MODULE](stateCopy, mockNamespace);
expect(stateCopy.modules).toEqual([mockNamespace]);
});
});
});
......@@ -45,7 +45,7 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
subject.perform!
expect(pipeline.config_source).to eq 'auto_devops_source'
template = Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps')
template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
expect(command.config_content).to eq(template.content)
end
end
......@@ -78,7 +78,7 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
expect(pipeline.config_source).to eq 'auto_devops_source'
expect(pipeline.pipeline_config).to be_nil
template = Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps')
template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
expect(command.config_content).to eq(template.content)
end
end
......@@ -91,7 +91,7 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
expect(pipeline.config_source).to eq 'auto_devops_source'
expect(pipeline.pipeline_config).to be_nil
template = Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps')
template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
expect(command.config_content).to eq(template.content)
end
end
......@@ -122,34 +122,13 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
expect(project).to receive(:auto_devops_enabled?).and_return(true)
end
context 'when beta is enabled' do
before do
stub_feature_flags(auto_devops_beta: true)
end
it 'returns the content of AutoDevops template' do
subject.perform!
expect(pipeline.config_source).to eq 'auto_devops_source'
expect(pipeline.pipeline_config).to be_nil
template = Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps')
expect(command.config_content).to eq(template.content)
end
end
context 'when beta is disabled' do
before do
stub_feature_flags(auto_devops_beta: false)
end
it 'returns the content of AutoDevops template' do
subject.perform!
it 'returns the content of AutoDevops template' do
subject.perform!
expect(pipeline.config_source).to eq 'auto_devops_source'
expect(pipeline.pipeline_config).to be_nil
template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
expect(command.config_content).to eq(template.content)
end
expect(pipeline.config_source).to eq 'auto_devops_source'
expect(pipeline.pipeline_config).to be_nil
template = Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps')
expect(command.config_content).to eq(template.content)
end
end
......@@ -285,7 +264,7 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
<<~EOY
---
include:
- template: Beta/Auto-DevOps.gitlab-ci.yml
- template: Auto-DevOps.gitlab-ci.yml
EOY
end
......@@ -293,40 +272,12 @@ describe Gitlab::Ci::Pipeline::Chain::Config::Content do
expect(project).to receive(:auto_devops_enabled?).and_return(true)
end
context 'when beta is enabled' do
before do
stub_feature_flags(auto_devops_beta: true)
end
it 'builds root config including the auto-devops template' do
subject.perform!
expect(pipeline.config_source).to eq 'auto_devops_source'
expect(pipeline.pipeline_config.content).to eq(config_content_result)
expect(command.config_content).to eq(config_content_result)
end
end
context 'when beta is disabled' do
before do
stub_feature_flags(auto_devops_beta: false)
end
let(:config_content_result) do
<<~EOY
---
include:
- template: Auto-DevOps.gitlab-ci.yml
EOY
end
it 'builds root config including the auto-devops template' do
subject.perform!
it 'builds root config including the auto-devops template' do
subject.perform!
expect(pipeline.config_source).to eq 'auto_devops_source'
expect(pipeline.pipeline_config.content).to eq(config_content_result)
expect(command.config_content).to eq(config_content_result)
end
expect(pipeline.config_source).to eq 'auto_devops_source'
expect(pipeline.pipeline_config.content).to eq(config_content_result)
expect(command.config_content).to eq(config_content_result)
end
end
......
......@@ -133,8 +133,6 @@ describe 'Auto-DevOps.gitlab-ci.yml' do
end
with_them do
subject(:template) { Gitlab::Template::GitlabCiYmlTemplate.find('Beta/Auto-DevOps') }
let(:user) { create(:admin) }
let(:project) { create(:project, :custom_repo, files: files) }
let(:service) { Ci::CreatePipelineService.new(project, user, ref: 'master' ) }
......
......@@ -90,5 +90,13 @@ describe Gitlab::Database::BatchCount do
[1, 2, 4, 5, 6].each { |i| expect(described_class.batch_distinct_count(model, column, batch_size: i)).to eq(2) }
end
it 'counts with a start and finish' do
expect(described_class.batch_distinct_count(model, column, start: model.minimum(column), finish: model.maximum(column))).to eq(2)
end
it 'counts with User min and max as start and finish' do
expect(described_class.batch_distinct_count(model, column, start: User.minimum(:id), finish: User.maximum(:id))).to eq(2)
end
end
end
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