Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-04-22 21:10:00 +00:00
parent db061f4432
commit 22e60f1c61
39 changed files with 252 additions and 215 deletions

View file

@ -613,18 +613,6 @@ Style/StringLiteralsInInterpolation:
Style/SymbolProc:
Enabled: false
# Offense count: 7
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, AllowSafeAssignment.
# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex
Style/TernaryParentheses:
Exclude:
- 'app/finders/projects_finder.rb'
- 'app/helpers/namespaces_helper.rb'
- 'lib/gitlab/ci/build/artifacts/metadata/entry.rb'
- 'spec/requests/api/pipeline_schedules_spec.rb'
- 'spec/support/capybara.rb'
# Offense count: 99
# Cop supports --auto-correct.
Style/UnneededInterpolation:

View file

@ -353,7 +353,7 @@ export default {
v-if="isSettingsShown"
ref="settingsBtn"
:aria-label="__(`List settings`)"
class="no-drag rounded-right"
class="no-drag rounded-right js-board-settings-button"
title="List settings"
type="button"
@click="openSidebarSettings"

View file

@ -8,6 +8,8 @@ export const ListType = {
blank: 'blank',
};
export const inactiveListId = 0;
export default {
ListType,
};

View file

@ -216,7 +216,12 @@ export default {
if (entry.type === 'blob') {
if (tempFile) {
// Since we only support one list of file changes, it's safe to just remove from both
// changed and staged. Otherwise, we'd need to somehow evaluate the difference between
// changed and HEAD.
// https://gitlab.com/gitlab-org/create-stage/-/issues/12669
state.changedFiles = state.changedFiles.filter(f => f.path !== path);
state.stagedFiles = state.stagedFiles.filter(f => f.path !== path);
} else {
state.changedFiles = state.changedFiles.concat(entry);
}

View file

@ -14,7 +14,7 @@ import {
GlModalDirective,
GlTooltipDirective,
} from '@gitlab/ui';
import PanelType from './panel_type_with_alerts.vue';
import DashboardPanel from './dashboard_panel.vue';
import { s__ } from '~/locale';
import createFlash from '~/flash';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -37,7 +37,7 @@ import { defaultTimeRange, timeRanges } from '~/vue_shared/constants';
export default {
components: {
VueDraggable,
PanelType,
DashboardPanel,
Icon,
GlDeprecatedButton,
GlDropdown,
@ -558,7 +558,7 @@ export default {
>
<div
v-for="(graphData, graphIndex) in groupData.panels"
:key="`panel-type-${graphIndex}`"
:key="`dashboard-panel-${graphIndex}`"
class="col-12 col-lg-6 px-2 mb-2 draggable"
:class="{ 'draggable-enabled': isRearrangingPanels }"
>
@ -573,7 +573,7 @@ export default {
</a>
</div>
<panel-type
<dashboard-panel
:clipboard-text="generateLink(groupData.group, graphData.title, graphData.y_label)"
:graph-data="graphData"
:alerts-endpoint="alertsEndpoint"

View file

@ -26,6 +26,7 @@ import MonitorBarChart from './charts/bar.vue';
import MonitorStackedColumnChart from './charts/stacked_column.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import AlertWidget from './alert_widget.vue';
import { timeRangeToUrl, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
const events = {
@ -40,6 +41,7 @@ export default {
MonitorColumnChart,
MonitorBarChart,
MonitorStackedColumnChart,
AlertWidget,
GlIcon,
GlLoadingIcon,
GlTooltip,
@ -78,11 +80,22 @@ export default {
required: false,
default: 'monitoringDashboard',
},
alertsEndpoint: {
type: String,
required: false,
default: null,
},
prometheusAlertsAvailable: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
showTitleTooltip: false,
zoomedTimeRange: null,
allAlerts: {},
};
},
computed: {
@ -104,14 +117,13 @@ export default {
timeRange(state) {
return state[this.namespace].timeRange;
},
metricsSavedToDb(state, getters) {
return getters[`${this.namespace}/metricsSavedToDb`];
},
}),
title() {
return this.graphData.title || '';
},
alertWidgetAvailable() {
// This method is extended by ee functionality
return false;
},
graphDataHasResult() {
return (
this.graphData.metrics &&
@ -165,6 +177,18 @@ export default {
editCustomMetricLinkText() {
return n__('Metrics|Edit metric', 'Metrics|Edit metrics', this.graphData.metrics.length);
},
hasMetricsInDb() {
const { metrics = [] } = this.graphData;
return metrics.some(({ metricId }) => this.metricsSavedToDb.includes(metricId));
},
alertWidgetAvailable() {
return (
this.prometheusAlertsAvailable &&
this.alertsEndpoint &&
this.graphData &&
this.hasMetricsInDb
);
},
},
mounted() {
this.refreshTitleTooltip();
@ -200,6 +224,13 @@ export default {
this.zoomedTimeRange = { start, end };
this.$emit(events.timeRangeZoom, { start, end });
},
setAlerts(alertPath, alertAttributes) {
if (alertAttributes) {
this.$set(this.allAlerts, alertPath, alertAttributes);
} else {
this.$delete(this.allAlerts, alertPath);
}
},
},
panelTypes,
};

View file

@ -1,6 +1,6 @@
<script>
import { mapState, mapActions } from 'vuex';
import PanelType from '~/monitoring/components/panel_type_with_alerts.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { defaultTimeRange } from '~/vue_shared/constants';
import { timeRangeFromUrl, removeTimeRangeParams } from '../../utils';
@ -10,7 +10,7 @@ let sidebarMutationObserver;
export default {
components: {
PanelType,
DashboardPanel,
},
props: {
containerClass: {
@ -113,9 +113,9 @@ export default {
</script>
<template>
<div class="metrics-embed p-0 d-flex flex-wrap" :class="embedClass">
<panel-type
<dashboard-panel
v-for="(graphData, graphIndex) in charts"
:key="`panel-type-${graphIndex}`"
:key="`dashboard-panel-${graphIndex}`"
:class="panelClass"
:graph-data="graphData"
:group-id="dashboardUrl"

View file

@ -1,55 +0,0 @@
<script>
import { mapGetters } from 'vuex';
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import CePanelType from '~/monitoring/components/panel_type.vue';
import AlertWidget from './alert_widget.vue';
export default {
components: {
AlertWidget,
CustomMetricsFormFields,
},
extends: CePanelType,
props: {
alertsEndpoint: {
type: String,
required: false,
default: null,
},
prometheusAlertsAvailable: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
allAlerts: {},
};
},
computed: {
...mapGetters('monitoringDashboard', ['metricsSavedToDb']),
hasMetricsInDb() {
const { metrics = [] } = this.graphData;
return metrics.some(({ metricId }) => this.metricsSavedToDb.includes(metricId));
},
alertWidgetAvailable() {
return (
this.prometheusAlertsAvailable &&
this.alertsEndpoint &&
this.graphData &&
this.hasMetricsInDb
);
},
},
methods: {
setAlerts(alertPath, alertAttributes) {
if (alertAttributes) {
this.$set(this.allAlerts, alertPath, alertAttributes);
} else {
this.$delete(this.allAlerts, alertPath);
}
},
},
};
</script>

View file

@ -0,0 +1,9 @@
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
import initAlertsSettings from '~/alerts_service_settings';
document.addEventListener('DOMContentLoaded', () => {
const integrationSettingsForm = new IntegrationSettingsForm('.js-integration-settings-form');
integrationSettingsForm.init();
initAlertsSettings(document.querySelector('.js-alerts-service-settings'));
});

View file

@ -182,7 +182,7 @@ export default {
<gl-sprintf
:message="
s__(
'ContainerRegistry|Wildcards such as %{codeStart}.*-stable%{codeEnd} or %{codeStart}production/.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
'ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
)
"
>

View file

@ -407,7 +407,7 @@ export class SearchAutocomplete {
disableAutocomplete() {
if (!this.searchInput.hasClass('js-autocomplete-disabled') && this.dropdown.hasClass('show')) {
this.searchInput.addClass('js-autocomplete-disabled');
this.dropdown.dropdown('toggle');
this.dropdownToggle.dropdown('toggle');
this.restoreMenu();
}
}

View file

@ -151,11 +151,11 @@ class ProjectsFinder < UnionFinder
end
def by_personal(items)
(params[:personal].present? && current_user) ? items.personal(current_user) : items
params[:personal].present? && current_user ? items.personal(current_user) : items
end
def by_starred(items)
(params[:starred].present? && current_user) ? items.starred_by(current_user) : items
params[:starred].present? && current_user ? items.starred_by(current_user) : items
end
def by_trending(items)

View file

@ -80,8 +80,8 @@ module NamespacesHelper
visibility_level: n.visibility_level_value,
visibility: n.visibility,
name: n.name,
show_path: (type == 'group') ? group_path(n) : user_path(n),
edit_path: (type == 'group') ? edit_group_path(n) : nil
show_path: type == 'group' ? group_path(n) : user_path(n),
edit_path: type == 'group' ? edit_group_path(n) : nil
}]
end

View file

@ -0,0 +1,5 @@
---
title: Fix an issue where the Search dropdown results would not be clickable.
merge_request: 30087
author: mbergeron
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Fix Web IDE handling of deleting newly added files
merge_request: 29783
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Update the example regex in the image expiration policy UI
merge_request: 29348
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: Fix Service Templates missing Active toggle
merge_request: 29936
author:
type: fixed

View file

@ -132,7 +132,7 @@ The following API resources are available outside of project and group contexts
| [License](license.md) **(CORE ONLY)** | `/license` |
| [Markdown](markdown.md) | `/markdown` |
| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
| [Metrics dashboard annotations](metrics_dashboard_annotations.md) | `/environments/:id/metrics_dashboard/annotations` |
| [Metrics dashboard annotations](metrics_dashboard_annotations.md) | `/environments/:id/metrics_dashboard/annotations`, `/clusters/:id/metrics_dashboard/annotations` |
| [Namespaces](namespaces.md) | `/namespaces` |
| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |

View file

@ -18,6 +18,7 @@ Feature.enable(:metrics_dashboard_annotations)
```plaintext
POST /environments/:id/metrics_dashboard/annotations/
POST /clusters/:id/metrics_dashboard/annotations/
```
Parameters:

View file

@ -107,7 +107,7 @@ The following table lists available parameters for jobs:
| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
| [`environment`](#environment) | Name of an environment to which the job deploys. Also available: `environment:name`, `environment:url`, `environment:on_stop`, `environment:auto_stop_in` and `environment:action`. |
| [`cache`](#cache) | List of files that should be cached between subsequent runs. Also available: `cache:paths`, `cache:key`, `cache:untracked`, and `cache:policy`. |
| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`, `artifacts:reports:junit`, and `artifacts:reports:cobertura`.<br><br>In GitLab [Enterprise Edition](https://about.gitlab.com/pricing/), these are available: `artifacts:reports:codequality`, `artifacts:reports:sast`, `artifacts:reports:dependency_scanning`, `artifacts:reports:container_scanning`, `artifacts:reports:dast`, `artifacts:reports:license_management`, `artifacts:reports:performance` and `artifacts:reports:metrics`. |
| [`artifacts`](#artifacts) | List of files and directories to attach to a job on success. Also available: `artifacts:paths`, `artifacts:expose_as`, `artifacts:name`, `artifacts:untracked`, `artifacts:when`, `artifacts:expire_in`, `artifacts:reports`, `artifacts:reports:junit`, `artifacts:reports:cobertura`, and `artifacts:reports:terraform`.<br><br>In GitLab [Enterprise Edition](https://about.gitlab.com/pricing/), these are available: `artifacts:reports:codequality`, `artifacts:reports:sast`, `artifacts:reports:dependency_scanning`, `artifacts:reports:container_scanning`, `artifacts:reports:dast`, `artifacts:reports:license_management`, `artifacts:reports:performance` and `artifacts:reports:metrics`. |
| [`dependencies`](#dependencies) | Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from. |
| [`coverage`](#coverage) | Code coverage settings for a given job. |
| [`retry`](#retry) | When and how many times a job can be auto-retried in case of a failure. |
@ -3004,6 +3004,14 @@ and will be automatically shown in merge requests.
Cobertura was originally developed for Java, but there are many
third party ports for other languages like JavaScript, Python, Ruby, etc.
##### `artifacts:reports:terraform`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/207527) in GitLab 12.10. Requires [GitLab Runner](https://docs.gitlab.com/runner/) 11.5 and above.
The `terraform` report collects Terraform `tfplan.json` files. The
collected Terraform plan reports will be uploaded to GitLab as
artifacts and will be automatically shown in merge requests.
##### `artifacts:reports:codequality` **(STARTER)**
> Introduced in GitLab 11.5. Requires GitLab Runner 11.5 and above.

View file

@ -300,6 +300,9 @@ as even native users of English might misunderstand them.
- Instead of "i.e.," use "that is."
- Instead of "e.g.," use "for example," "such as," "for instance," or "like."
- Instead of "etc.," either use "and so on" or consider editing it out, since it can be vague.
- Avoid using the word *Currently* when talking about the product or its
features. The documentation describes the product as it is, and not as it
will be at some indeterminate point in the future.
### Contractions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View file

@ -511,7 +511,7 @@ then goes through a process of excluding tags from it until only the ones to be
To manage project expiration policy, navigate to **{settings}** **Settings > CI/CD > Container Registry tag expiration policy**.
![Expiration Policy App](img/expiration-policy-app.png)
![Expiration Policy App](img/expiration_policy_app_v13_0.png)
The UI allows you to configure the following:

View file

@ -123,6 +123,24 @@ to integrate with.
1. Provide the domain name or IP address of your server, for example `http://thanos.example.com/` or `http://192.0.2.1/`.
1. Click **Save changes**.
### Precedence with multiple Prometheus configurations
Although you can enable both a [manual configuration](#manual-configuration-of-prometheus)
and [auto configuration](#managed-prometheus-on-kubernetes) of Prometheus, only
one of them will be used:
- If you have enabled a
[Prometheus manual configuration](#manual-configuration-of-prometheus)
and a [managed Prometheus on Kubernetes](#managed-prometheus-on-kubernetes),
the manual configuration takes precedence and is used to run queries from
[dashboards](#defining-custom-dashboards-per-project) and [custom metrics](#adding-custom-metrics).
- If you have managed Prometheus applications installed on Kubernetes clusters
at **different** levels (project, group, instance), the order of precedence is described in
[Cluster precedence](../../instance/clusters/index.md#cluster-precedence).
- If you have managed Prometheus applications installed on multiple Kubernetes
clusters at the **same** level, the Prometheus application of a cluster with a
matching [environment scope](../../../ci/environments.md#scoping-environments-with-specs) is used.
## Monitoring CI/CD Environments
Once configured, GitLab will attempt to retrieve performance metrics for any

View file

@ -50,7 +50,7 @@ module Gitlab
end
def basename
(directory? && !blank_node?) ? name + '/' : name
directory? && !blank_node? ? name + '/' : name
end
def name

View file

@ -5600,6 +5600,9 @@ msgstr ""
msgid "ContainerRegistry|Quick Start"
msgstr ""
msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
@ -5686,9 +5689,6 @@ msgstr ""
msgid "ContainerRegistry|We are having trouble connecting to Docker, which could be due to an issue with your project name or path. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""
msgid "ContainerRegistry|Wildcards such as %{codeStart}.*-stable%{codeEnd} or %{codeStart}production/.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
msgstr ""
msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""

View file

@ -21,7 +21,7 @@ module QA
element :duplicate_dashboard_filename_field
end
view 'app/assets/javascripts/monitoring/components/panel_type.vue' do
view 'app/assets/javascripts/monitoring/components/dashboard_panel.vue' do
element :prometheus_graph_widgets
element :prometheus_widgets_dropdown
element :alert_widget_menu_item

View file

@ -30,4 +30,14 @@ describe 'IDE user commits changes', :js do
expect(project.repository.blob_at('master', 'foo/bar/.gitkeep')).to be_nil
expect(project.repository.blob_at('master', 'foo/bar/lorem_ipsum.md').data).to eql(content)
end
it 'user adds then deletes new file' do
ide_create_new_file('foo/bar/lorem_ipsum.md')
expect(page).to have_selector(ide_commit_tab_selector)
ide_delete_file('foo/bar/lorem_ipsum.md')
expect(page).not_to have_selector(ide_commit_tab_selector)
end
end

View file

@ -273,7 +273,7 @@ describe('Multi-file store mutations', () => {
expect(localState.changedFiles).toEqual([]);
});
it('removes tempFile from changedFiles when deleted', () => {
it('removes tempFile from changedFiles and stagedFiles when deleted', () => {
localState.entries.filePath = {
path: 'filePath',
deleted: false,
@ -282,10 +282,12 @@ describe('Multi-file store mutations', () => {
};
localState.changedFiles.push({ ...localState.entries.filePath });
localState.stagedFiles.push({ ...localState.entries.filePath });
mutations.DELETE_ENTRY(localState, 'filePath');
expect(localState.changedFiles).toEqual([]);
expect(localState.stagedFiles).toEqual([]);
});
it('bursts unused seal', () => {

View file

@ -1,10 +1,13 @@
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { setTestTimeout } from 'helpers/timeout';
import invalidUrl from '~/lib/utils/invalid_url';
import axios from '~/lib/utils/axios_utils';
import { GlDropdownItem } from '@gitlab/ui';
import AlertWidget from '~/monitoring/components/alert_widget.vue';
import PanelType from '~/monitoring/components/panel_type.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import {
anomalyMockGraphData,
mockLogsHref,
@ -40,7 +43,7 @@ const mocks = {
},
};
describe('Panel Type component', () => {
describe('Dashboard Panel', () => {
let axiosMock;
let store;
let state;
@ -54,7 +57,7 @@ describe('Panel Type component', () => {
const findContextualMenu = () => wrapper.find({ ref: 'contextualMenu' });
const createWrapper = props => {
wrapper = shallowMount(PanelType, {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
...props,
@ -343,7 +346,7 @@ describe('Panel Type component', () => {
describe('when downloading metrics data as CSV', () => {
beforeEach(() => {
wrapper = shallowMount(PanelType, {
wrapper = shallowMount(DashboardPanel, {
propsData: {
clipboardText: exampleText,
graphData: {
@ -392,7 +395,7 @@ describe('Panel Type component', () => {
store.registerModule(mockNamespace, monitoringDashboard);
store.state.embedGroup.modules.push(mockNamespace);
wrapper = shallowMount(PanelType, {
wrapper = shallowMount(DashboardPanel, {
propsData: {
graphData,
namespace: mockNamespace,
@ -432,4 +435,52 @@ describe('Panel Type component', () => {
expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true);
});
});
describe('panel alerts', () => {
const setMetricsSavedToDb = val =>
monitoringDashboard.getters.metricsSavedToDb.mockReturnValue(val);
const findAlertsWidget = () => wrapper.find(AlertWidget);
const findMenuItemAlert = () =>
wrapper.findAll(GlDropdownItem).filter(i => i.text() === 'Alerts');
beforeEach(() => {
jest.spyOn(monitoringDashboard.getters, 'metricsSavedToDb').mockReturnValue([]);
store = new Vuex.Store({
modules: {
monitoringDashboard,
},
});
createWrapper();
});
describe.each`
desc | metricsSavedToDb | propsData | isShown
${'with permission and no metrics in db'} | ${[]} | ${{}} | ${false}
${'with permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{}} | ${true}
${'without permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{ prometheusAlertsAvailable: false }} | ${false}
${'with permission and unrelated metrics in db'} | ${['another_metric_id']} | ${{}} | ${false}
`('$desc', ({ metricsSavedToDb, isShown, propsData }) => {
const showsDesc = isShown ? 'shows' : 'does not show';
beforeEach(() => {
setMetricsSavedToDb(metricsSavedToDb);
createWrapper({
alertsEndpoint: '/endpoint',
prometheusAlertsAvailable: true,
...propsData,
});
return wrapper.vm.$nextTick();
});
it(`${showsDesc} alert widget`, () => {
expect(findAlertsWidget().exists()).toBe(isShown);
});
it(`${showsDesc} alert configuration`, () => {
expect(findMenuItemAlert().exists()).toBe(isShown);
});
});
});
});

View file

@ -12,7 +12,7 @@ import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_p
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import PanelType from '~/monitoring/components/panel_type_with_alerts.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import { setupStoreWithDashboard, setMetricResult, setupStoreWithData } from '../store_utils';
@ -48,6 +48,7 @@ describe('Dashboard', () => {
fetchData: jest.fn(),
},
store,
stubs: ['graph-group', 'dashboard-panel'],
...options,
});
};
@ -126,10 +127,7 @@ describe('Dashboard', () => {
});
it('hides the group panels when showPanels is false', () => {
createMountedWrapper(
{ hasMetrics: true, showPanels: false },
{ stubs: ['graph-group', 'panel-type'] },
);
createMountedWrapper({ hasMetrics: true, showPanels: false });
setupStoreWithData(wrapper.vm.$store);
@ -142,7 +140,7 @@ describe('Dashboard', () => {
it('fetches the metrics data with proper time window', () => {
jest.spyOn(store, 'dispatch');
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
createMountedWrapper({ hasMetrics: true });
wrapper.vm.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
@ -157,7 +155,7 @@ describe('Dashboard', () => {
describe('when all requests have been commited by the store', () => {
beforeEach(() => {
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
@ -186,7 +184,7 @@ describe('Dashboard', () => {
});
it('hides the environments dropdown list when there is no environments', () => {
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
createMountedWrapper({ hasMetrics: true });
setupStoreWithDashboard(wrapper.vm.$store);
@ -196,7 +194,7 @@ describe('Dashboard', () => {
});
it('renders the datetimepicker dropdown', () => {
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
@ -206,7 +204,7 @@ describe('Dashboard', () => {
});
it('renders the refresh dashboard button', () => {
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
createMountedWrapper({ hasMetrics: true });
setupStoreWithData(wrapper.vm.$store);
@ -249,13 +247,7 @@ describe('Dashboard', () => {
describe('searchable environments dropdown', () => {
beforeEach(() => {
createMountedWrapper(
{ hasMetrics: true },
{
attachToDocument: true,
stubs: ['graph-group', 'panel-type'],
},
);
createMountedWrapper({ hasMetrics: true }, { attachToDocument: true });
setupStoreWithData(wrapper.vm.$store);
@ -465,7 +457,7 @@ describe('Dashboard', () => {
describe('Dashboard dropdown', () => {
beforeEach(() => {
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
createMountedWrapper({ hasMetrics: true });
wrapper.vm.$store.commit(
`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
@ -484,15 +476,12 @@ describe('Dashboard', () => {
describe('external dashboard link', () => {
beforeEach(() => {
createMountedWrapper(
{
hasMetrics: true,
showPanels: false,
showTimeWindowDropdown: false,
externalDashboardUrl: '/mockUrl',
},
{ stubs: ['graph-group', 'panel-type'] },
);
createMountedWrapper({
hasMetrics: true,
showPanels: false,
showTimeWindowDropdown: false,
externalDashboardUrl: '/mockUrl',
});
return wrapper.vm.$nextTick();
});
@ -511,7 +500,7 @@ describe('Dashboard', () => {
const getClipboardTextAt = i =>
wrapper
.findAll(PanelType)
.findAll(DashboardPanel)
.at(i)
.props('clipboardText');

View file

@ -27,7 +27,7 @@ describe('dashboard invalid url parameters', () => {
wrapper = mount(Dashboard, {
propsData: { ...propsData, ...props },
store,
stubs: ['graph-group', 'panel-type'],
stubs: ['graph-group', 'dashboard-panel'],
...options,
});
};

View file

@ -1,6 +1,6 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import PanelType from '~/monitoring/components/panel_type_with_alerts.vue';
import DashboardPanel from '~/monitoring/components/dashboard_panel.vue';
import { TEST_HOST } from 'helpers/test_constants';
import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue';
import { groups, initialState, metricsData, metricsWithData } from './mock_data';
@ -62,7 +62,7 @@ describe('MetricEmbed', () => {
it('shows an empty state when no metrics are present', () => {
expect(wrapper.find('.metrics-embed').exists()).toBe(true);
expect(wrapper.find(PanelType).exists()).toBe(false);
expect(wrapper.find(DashboardPanel).exists()).toBe(false);
});
});
@ -90,12 +90,12 @@ describe('MetricEmbed', () => {
it('shows a chart when metrics are present', () => {
expect(wrapper.find('.metrics-embed').exists()).toBe(true);
expect(wrapper.find(PanelType).exists()).toBe(true);
expect(wrapper.findAll(PanelType).length).toBe(2);
expect(wrapper.find(DashboardPanel).exists()).toBe(true);
expect(wrapper.findAll(DashboardPanel).length).toBe(2);
});
it('includes groupId with dashboardUrl', () => {
expect(wrapper.find(PanelType).props('groupId')).toBe(TEST_HOST);
expect(wrapper.find(DashboardPanel).props('groupId')).toBe(TEST_HOST);
});
});
});

View file

@ -1,73 +0,0 @@
import Vuex from 'vuex';
import { shallowMount } from '@vue/test-utils';
import { GlDropdownItem } from '@gitlab/ui';
import { monitoringDashboard } from '~/monitoring/stores';
import PanelType from '~/monitoring/components/panel_type_with_alerts.vue';
import AlertWidget from '~/monitoring/components/alert_widget.vue';
import { graphData } from 'jest/monitoring/fixture_data';
global.URL.createObjectURL = jest.fn();
describe('Panel Type', () => {
let store;
let wrapper;
const setMetricsSavedToDb = val =>
monitoringDashboard.getters.metricsSavedToDb.mockReturnValue(val);
const findAlertsWidget = () => wrapper.find(AlertWidget);
const findMenuItemAlert = () =>
wrapper.findAll(GlDropdownItem).filter(i => i.text() === 'Alerts');
const mockPropsData = {
graphData,
clipboardText: 'example_text',
alertsEndpoint: '/endpoint',
prometheusAlertsAvailable: true,
};
const createWrapper = propsData => {
wrapper = shallowMount(PanelType, {
propsData: {
...mockPropsData,
...propsData,
},
store,
});
};
beforeEach(() => {
jest.spyOn(monitoringDashboard.getters, 'metricsSavedToDb').mockReturnValue([]);
store = new Vuex.Store({
modules: {
monitoringDashboard,
},
});
});
describe('panel type alerts', () => {
describe.each`
desc | metricsSavedToDb | propsData | isShown
${'with license and no metrics in db'} | ${[]} | ${{}} | ${false}
${'with license and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{}} | ${true}
${'without license and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{ prometheusAlertsAvailable: false }} | ${false}
${'with license and unrelated metrics in db'} | ${['another_metric_id']} | ${{}} | ${false}
`('$desc', ({ metricsSavedToDb, isShown, propsData }) => {
const showsDesc = isShown ? 'shows' : 'does not show';
beforeEach(() => {
setMetricsSavedToDb(metricsSavedToDb);
createWrapper(propsData);
return wrapper.vm.$nextTick();
});
it(`${showsDesc} alert widget`, () => {
expect(findAlertsWidget().exists()).toBe(isShown);
});
it(`${showsDesc} alert configuration`, () => {
expect(findMenuItemAlert().exists()).toBe(isShown);
});
});
});
});

View file

@ -188,4 +188,28 @@ describe('Search autocomplete dropdown', () => {
// example) on JavaScript-created keypresses.
expect(submitSpy).not.toHaveBeenTriggered();
});
describe('disableAutocomplete', function() {
beforeEach(function() {
widget.enableAutocomplete();
});
it('should close the Dropdown', function() {
const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown');
widget.dropdown.addClass('show');
widget.disableAutocomplete();
expect(toggleSpy).toHaveBeenCalledWith('toggle');
});
});
describe('enableAutocomplete', function() {
it('should open the Dropdown', function() {
const toggleSpy = spyOn(widget.dropdownToggle, 'dropdown');
widget.enableAutocomplete();
expect(toggleSpy).toHaveBeenCalledWith('toggle');
});
});
});

View file

@ -67,7 +67,7 @@ describe API::PipelineSchedules do
end
def active?(str)
(str == 'active') ? true : false
str == 'active'
end
end
end

View file

@ -7,7 +7,7 @@ require 'capybara-screenshot/rspec'
require 'selenium-webdriver'
# Give CI some extra time
timeout = (ENV['CI'] || ENV['CI_SERVER']) ? 60 : 30
timeout = ENV['CI'] || ENV['CI_SERVER'] ? 60 : 30
# Define an error class for JS console messages
JSConsoleError = Class.new(StandardError)

View file

@ -32,6 +32,10 @@ module WebIdeSpecHelpers
page.find('.ide-tree-actions')
end
def ide_tab_selector(mode)
".js-ide-#{mode}-mode"
end
def ide_file_row_open?(row)
row.matches_css?('.is-open')
end
@ -106,14 +110,14 @@ module WebIdeSpecHelpers
evaluate_script("monaco.editor.getModel('#{uri}').getValue()")
end
def ide_commit
ide_switch_mode('commit')
commit_to_current_branch
def ide_commit_tab_selector
ide_tab_selector('commit')
end
def ide_switch_mode(mode)
find(".js-ide-#{mode}-mode").click
def ide_commit
find(ide_commit_tab_selector).click
commit_to_current_branch
end
private