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

Add latest changes from gitlab-org/gitlab@master

parent 456a7247
No related branches found
No related tags found
No related merge requests found
Showing
with 451 additions and 132 deletions
Loading
Loading
@@ -257,7 +257,7 @@ excluded_attributes:
- :token
- :token_encrypted
services:
- :template
- :instance
error_tracking_setting:
- :encrypted_token
- :encrypted_token_iv
Loading
Loading
Loading
Loading
@@ -179,7 +179,7 @@ module Gitlab
 
# rubocop: disable CodeReuse/ActiveRecord
def services_usage
service_counts = count(Service.active.where(template: false).where.not(type: 'JiraService').group(:type), fallback: Hash.new(-1))
service_counts = count(Service.active.where(instance: false).where.not(type: 'JiraService').group(:type), fallback: Hash.new(-1))
 
results = Service.available_services_names.each_with_object({}) do |service_name, response|
response["projects_#{service_name}_active".to_sym] = service_counts["#{service_name}_service".camelize] || 0
Loading
Loading
Loading
Loading
@@ -10903,9 +10903,6 @@ msgstr ""
msgid "Kubernetes cluster was successfully updated."
msgstr ""
 
msgid "Kubernetes configured"
msgstr ""
msgid "Kubernetes deployment not found"
msgstr ""
 
Loading
Loading
@@ -12121,6 +12118,9 @@ msgstr ""
msgid "Metrics|Validating query"
msgstr ""
 
msgid "Metrics|View logs"
msgstr ""
msgid "Metrics|Y-axis label"
msgstr ""
 
Loading
Loading
# frozen_string_literal: true
 
module QA
context 'Manage', :orchestrated, :mattermost, quarantine: 'https://gitlab.com/gitlab-org/gitlab/issues/202069' do
context 'Manage', :orchestrated, :mattermost do
describe 'Mattermost login' do
it 'user logs into Mattermost using GitLab OAuth' do
Flow::Login.sign_in
Loading
Loading
Loading
Loading
@@ -15,11 +15,11 @@ describe Admin::ServicesController do
Service.available_services_names.each do |service_name|
context "#{service_name}" do
let!(:service) do
service_template = "#{service_name}_service".camelize.constantize
service_template.where(template: true).first_or_create
service_instance = "#{service_name}_service".camelize.constantize
service_instance.where(instance: true).first_or_create
end
 
it 'successfully displays the template' do
it 'successfully displays the service' do
get :edit, params: { id: service.id }
 
expect(response).to have_gitlab_http_status(:ok)
Loading
Loading
@@ -34,7 +34,7 @@ describe Admin::ServicesController do
RedmineService.create(
project: project,
active: false,
template: true,
instance: true,
properties: {
project_url: 'http://abc',
issues_url: 'http://abc',
Loading
Loading
@@ -44,7 +44,7 @@ describe Admin::ServicesController do
end
 
it 'calls the propagation worker when service is active' do
expect(PropagateServiceTemplateWorker).to receive(:perform_async).with(service.id)
expect(PropagateInstanceLevelServiceWorker).to receive(:perform_async).with(service.id)
 
put :update, params: { id: service.id, service: { active: true } }
 
Loading
Loading
@@ -52,7 +52,7 @@ describe Admin::ServicesController do
end
 
it 'does not call the propagation worker when service is not active' do
expect(PropagateServiceTemplateWorker).not_to receive(:perform_async)
expect(PropagateInstanceLevelServiceWorker).not_to receive(:perform_async)
 
put :update, params: { id: service.id, service: { properties: {} } }
 
Loading
Loading
Loading
Loading
@@ -10,8 +10,6 @@ describe LfsRequest do
include LfsRequest
 
def show
storage_project
head :ok
end
 
Loading
Loading
@@ -38,22 +36,6 @@ describe LfsRequest do
stub_lfs_setting(enabled: true)
end
 
describe '#storage_project' do
it 'assigns the project as storage project' do
get :show, params: { id: project.id }
expect(assigns(:storage_project)).to eq(project)
end
it 'assigns the source of a forked project' do
forked_project = fork_project(project)
get :show, params: { id: forked_project.id }
expect(assigns(:storage_project)).to eq(project)
end
end
context 'user is authenticated without access to lfs' do
before do
allow(controller).to receive(:authenticate_user)
Loading
Loading
Loading
Loading
@@ -11,7 +11,14 @@ describe Dashboard::ProjectsController do
end
 
context 'user logged in' do
let(:user) { create(:user) }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:project2) { create(:project) }
before_all do
project.add_developer(user)
project2.add_developer(user)
end
 
before do
sign_in(user)
Loading
Loading
@@ -28,12 +35,7 @@ describe Dashboard::ProjectsController do
end
 
it 'orders the projects by last activity by default' do
project = create(:project)
project.add_developer(user)
project.update!(last_repository_updated_at: 3.days.ago, last_activity_at: 3.days.ago)
project2 = create(:project)
project2.add_developer(user)
project2.update!(last_repository_updated_at: 10.days.ago, last_activity_at: 10.days.ago)
 
get :index
Loading
Loading
@@ -42,12 +44,27 @@ describe Dashboard::ProjectsController do
end
 
context 'project sorting' do
let(:project) { create(:project) }
it_behaves_like 'set sort order from user preference' do
let(:sorting_param) { 'created_asc' }
end
end
context 'with search and sort parameters' do
render_views
shared_examples 'search and sort parameters' do |sort|
it 'returns a single project with no ambiguous column errors' do
get :index, params: { name: project2.name, sort: sort }
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:projects)).to eq([project2])
end
end
%w[latest_activity_desc latest_activity_asc stars_desc stars_asc created_desc].each do |sort|
it_behaves_like 'search and sort parameters', sort
end
end
end
end
 
Loading
Loading
Loading
Loading
@@ -154,12 +154,12 @@ describe Projects::ServicesController do
end
end
 
context 'when activating Jira service from a template' do
context 'when activating Jira service from instance level service' do
let(:service) do
create(:jira_service, project: project, template: true)
create(:jira_service, project: project, instance: true)
end
 
it 'activate Jira service from template' do
it 'activate Jira service from instance level service' do
expect(flash[:notice]).to eq 'Jira activated.'
end
end
Loading
Loading
Loading
Loading
@@ -33,7 +33,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
expect(page).not_to have_link('Enable Auto DevOps')
expect(page).not_to have_link('Auto DevOps enabled')
expect(page).not_to have_link('Add Kubernetes cluster')
expect(page).not_to have_link('Kubernetes configured')
expect(page).not_to have_link('Kubernetes')
end
end
end
Loading
Loading
@@ -100,7 +100,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
it 'no Kubernetes cluster button if can not manage clusters' do
page.within('.project-buttons') do
expect(page).not_to have_link('Add Kubernetes cluster')
expect(page).not_to have_link('Kubernetes configured')
expect(page).not_to have_link('Kubernetes')
end
end
end
Loading
Loading
@@ -308,7 +308,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project)
 
page.within('.project-buttons') do
expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster))
expect(page).to have_link('Kubernetes', href: project_cluster_path(project, cluster))
end
end
end
Loading
Loading
Loading
Loading
@@ -2736,7 +2736,7 @@ Service
when repository is empty
test runs execute
Template
.build_from_template
.build_from_instance
when template is invalid
sets service template to inactive when template is invalid
for pushover service
Loading
Loading
Loading
Loading
@@ -18,6 +18,15 @@ import * as iconUtils from '~/lib/utils/icon_utils';
const mockWidgets = 'mockWidgets';
 
const mockSvgPathContent = 'mockSvgPathContent';
jest.mock('lodash/throttle', () =>
// this throttle mock executes immediately
jest.fn(func => {
// eslint-disable-next-line no-param-reassign
func.cancel = jest.fn();
return func;
}),
);
jest.mock('~/lib/utils/icon_utils', () => ({
getSvgIconPathContent: jest.fn().mockImplementation(() => Promise.resolve(mockSvgPathContent)),
}));
Loading
Loading
@@ -94,6 +103,56 @@ describe('Time series component', () => {
});
});
 
describe('events', () => {
describe('datazoom', () => {
let eChartMock;
let startValue;
let endValue;
const findChart = () => timeSeriesChart.find({ ref: 'chart' });
beforeEach(done => {
eChartMock = {
handlers: {},
getOption: () => ({
dataZoom: [
{
startValue,
endValue,
},
],
}),
off: jest.fn(eChartEvent => {
delete eChartMock.handlers[eChartEvent];
}),
on: jest.fn((eChartEvent, fn) => {
eChartMock.handlers[eChartEvent] = fn;
}),
};
timeSeriesChart = makeTimeSeriesChart(mockGraphData);
timeSeriesChart.vm.$nextTick(() => {
findChart().vm.$emit('created', eChartMock);
done();
});
});
it('handles datazoom event from chart', () => {
startValue = 1577836800000; // 2020-01-01T00:00:00.000Z
endValue = 1577840400000; // 2020-01-01T01:00:00.000Z
eChartMock.handlers.datazoom();
expect(timeSeriesChart.emitted('datazoom')).toHaveLength(1);
expect(timeSeriesChart.emitted('datazoom')[0]).toEqual([
{
start: new Date(startValue).toISOString(),
end: new Date(endValue).toISOString(),
},
]);
});
});
});
describe('methods', () => {
describe('formatTooltipText', () => {
let mockDate;
Loading
Loading
Loading
Loading
@@ -73,12 +73,20 @@ describe('Dashboard', () => {
 
describe('no metrics are available yet', () => {
beforeEach(() => {
jest.spyOn(store, 'dispatch');
createShallowWrapper();
});
 
it('shows the environment selector', () => {
expect(findEnvironmentsDropdown().exists()).toBe(true);
});
it('sets endpoints: logs path', () => {
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/setEndpoints',
expect.objectContaining({ logsPath: propsData.logsPath }),
);
});
});
 
describe('no data found', () => {
Loading
Loading
@@ -94,6 +102,21 @@ describe('Dashboard', () => {
});
 
describe('request information to the server', () => {
it('calls to set time range and fetch data', () => {
jest.spyOn(store, 'dispatch');
createShallowWrapper({ hasMetrics: true }, { methods: {} });
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/setTimeRange',
expect.any(Object),
);
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
});
it('shows up a loading state', done => {
createShallowWrapper({ hasMetrics: true }, { methods: {} });
 
Loading
Loading
@@ -126,7 +149,7 @@ describe('Dashboard', () => {
.catch(done.fail);
});
 
it('fetches the metrics data with proper time window', done => {
it('fetches the metrics data with proper time window', () => {
jest.spyOn(store, 'dispatch');
 
createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] });
Loading
Loading
@@ -136,14 +159,9 @@ describe('Dashboard', () => {
environmentData,
);
 
wrapper.vm
.$nextTick()
.then(() => {
expect(store.dispatch).toHaveBeenCalled();
done();
})
.catch(done.fail);
return wrapper.vm.$nextTick().then(() => {
expect(store.dispatch).toHaveBeenCalled();
});
});
});
 
Loading
Loading
@@ -263,10 +281,6 @@ describe('Dashboard', () => {
return wrapper.vm.$nextTick();
});
 
afterEach(() => {
wrapper.destroy();
});
it('renders a search input', () => {
expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownSearch' }).exists()).toBe(true);
});
Loading
Loading
Loading
Loading
@@ -7,6 +7,7 @@ import { mockProjectDir } from '../mock_data';
 
import Dashboard from '~/monitoring/components/dashboard.vue';
import { createStore } from '~/monitoring/stores';
import { defaultTimeRange } from '~/monitoring/constants';
import { propsData } from '../init_utils';
 
jest.mock('~/flash');
Loading
Loading
@@ -17,16 +18,11 @@ describe('dashboard invalid url parameters', () => {
let wrapper;
let mock;
 
const fetchDataMock = jest.fn();
const createMountedWrapper = (props = { hasMetrics: true }, options = {}) => {
wrapper = mount(Dashboard, {
propsData: { ...propsData, ...props },
store,
stubs: ['graph-group', 'panel-type'],
methods: {
fetchData: fetchDataMock,
},
...options,
});
};
Loading
Loading
@@ -35,6 +31,8 @@ describe('dashboard invalid url parameters', () => {
 
beforeEach(() => {
store = createStore();
jest.spyOn(store, 'dispatch');
mock = new MockAdapter(axios);
});
 
Loading
Loading
@@ -43,7 +41,6 @@ describe('dashboard invalid url parameters', () => {
wrapper.destroy();
}
mock.restore();
fetchDataMock.mockReset();
queryToObject.mockReset();
});
 
Loading
Loading
@@ -53,15 +50,13 @@ describe('dashboard invalid url parameters', () => {
createMountedWrapper();
 
return wrapper.vm.$nextTick().then(() => {
expect(findDateTimePicker().props('value')).toMatchObject({
duration: { seconds: 28800 },
});
expect(findDateTimePicker().props('value')).toEqual(defaultTimeRange);
 
expect(fetchDataMock).toHaveBeenCalledTimes(1);
expect(fetchDataMock).toHaveBeenCalledWith({
start: expect.any(String),
end: expect.any(String),
});
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/setTimeRange',
expect.any(Object),
);
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
});
 
Loading
Loading
@@ -78,8 +73,8 @@ describe('dashboard invalid url parameters', () => {
return wrapper.vm.$nextTick().then(() => {
expect(findDateTimePicker().props('value')).toEqual(params);
 
expect(fetchDataMock).toHaveBeenCalledTimes(1);
expect(fetchDataMock).toHaveBeenCalledWith(params);
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setTimeRange', params);
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
});
 
Loading
Loading
@@ -91,15 +86,17 @@ describe('dashboard invalid url parameters', () => {
createMountedWrapper();
 
return wrapper.vm.$nextTick().then(() => {
expect(findDateTimePicker().props('value')).toMatchObject({
const expectedTimeRange = {
duration: { seconds: 60 * 2 },
});
};
 
expect(fetchDataMock).toHaveBeenCalledTimes(1);
expect(fetchDataMock).toHaveBeenCalledWith({
start: expect.any(String),
end: expect.any(String),
});
expect(findDateTimePicker().props('value')).toMatchObject(expectedTimeRange);
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/setTimeRange',
expectedTimeRange,
);
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
});
 
Loading
Loading
@@ -114,15 +111,13 @@ describe('dashboard invalid url parameters', () => {
return wrapper.vm.$nextTick().then(() => {
expect(createFlash).toHaveBeenCalled();
 
expect(findDateTimePicker().props('value')).toMatchObject({
duration: { seconds: 28800 },
});
expect(findDateTimePicker().props('value')).toEqual(defaultTimeRange);
 
expect(fetchDataMock).toHaveBeenCalledTimes(1);
expect(fetchDataMock).toHaveBeenCalledWith({
start: expect.any(String),
end: expect.any(String),
});
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/setTimeRange',
defaultTimeRange,
);
expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/fetchData', undefined);
});
});
 
Loading
Loading
@@ -137,7 +132,7 @@ describe('dashboard invalid url parameters', () => {
duration: { seconds: 120 },
});
 
// redirect to plus + new parameters
// redirect to with new parameters
expect(mergeUrlParams).toHaveBeenCalledWith({ duration_seconds: '120' }, toUrl);
expect(redirectTo).toHaveBeenCalledTimes(1);
});
Loading
Loading
Loading
Loading
@@ -26,10 +26,11 @@ describe('Embed', () => {
 
beforeEach(() => {
actions = {
setFeatureFlags: () => {},
setShowErrorBanner: () => {},
setEndpoints: () => {},
fetchMetricsData: () => {},
setFeatureFlags: jest.fn(),
setShowErrorBanner: jest.fn(),
setEndpoints: jest.fn(),
setTimeRange: jest.fn(),
fetchDashboard: jest.fn(),
};
 
metricsWithDataGetter = jest.fn();
Loading
Loading
@@ -76,6 +77,18 @@ describe('Embed', () => {
mountComponent();
});
 
it('calls actions to fetch data', () => {
const expectedTimeRangePayload = expect.objectContaining({
start: expect.any(String),
end: expect.any(String),
});
expect(actions.setTimeRange).toHaveBeenCalledTimes(1);
expect(actions.setTimeRange.mock.calls[0][1]).toEqual(expectedTimeRangePayload);
expect(actions.fetchDashboard).toHaveBeenCalled();
});
it('shows a chart when metrics are present', () => {
expect(wrapper.find('.metrics-embed').exists()).toBe(true);
expect(wrapper.find(PanelType).exists()).toBe(true);
Loading
Loading
Loading
Loading
@@ -15,6 +15,7 @@ export const propsData = {
clustersPath: '/path/to/clusters',
tagsPath: '/path/to/tags',
projectPath: '/path/to/project',
logsPath: '/path/to/logs',
defaultBranch: 'master',
metricsEndpoint: mockApiEndpoint,
deploymentsEndpoint: null,
Loading
Loading
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 PanelType from '~/monitoring/components/panel_type.vue';
import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
Loading
Loading
@@ -16,20 +17,25 @@ global.URL.createObjectURL = jest.fn();
describe('Panel Type component', () => {
let axiosMock;
let store;
let panelType;
const dashboardWidth = 100;
let state;
let wrapper;
const exampleText = 'example_text';
 
const createWrapper = props =>
shallowMount(PanelType, {
const createWrapper = props => {
wrapper = shallowMount(PanelType, {
propsData: {
...props,
},
store,
});
};
 
beforeEach(() => {
setTestTimeout(1000);
store = createStore();
state = store.state.monitoringDashboard;
axiosMock = new AxiosMockAdapter(axios);
});
 
Loading
Loading
@@ -44,19 +50,18 @@ describe('Panel Type component', () => {
graphDataNoResult.metrics[0].result = [];
 
beforeEach(() => {
panelType = createWrapper({
dashboardWidth,
createWrapper({
graphData: graphDataNoResult,
});
});
 
afterEach(() => {
panelType.destroy();
wrapper.destroy();
});
 
describe('Empty Chart component', () => {
beforeEach(() => {
glEmptyChart = panelType.find(EmptyChart);
glEmptyChart = wrapper.find(EmptyChart);
});
 
it('is a Vue instance', () => {
Loading
Loading
@@ -66,51 +71,126 @@ describe('Panel Type component', () => {
it('it receives a graph title', () => {
const props = glEmptyChart.props();
 
expect(props.graphTitle).toBe(panelType.vm.graphData.title);
expect(props.graphTitle).toBe(wrapper.vm.graphData.title);
});
});
});
 
describe('when graph data is available', () => {
beforeEach(() => {
store = createStore();
panelType = createWrapper({
dashboardWidth,
createWrapper({
graphData: graphDataPrometheusQueryRange,
});
});
 
afterEach(() => {
panelType.destroy();
wrapper.destroy();
});
 
it('sets no clipboard copy link on dropdown by default', () => {
const link = () => panelType.find('.js-chart-link');
const link = () => wrapper.find('.js-chart-link');
expect(link().exists()).toBe(false);
});
 
describe('Time Series Chart panel type', () => {
it('is rendered', () => {
expect(panelType.find(TimeSeriesChart).isVueInstance()).toBe(true);
expect(panelType.find(TimeSeriesChart).exists()).toBe(true);
expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true);
expect(wrapper.find(TimeSeriesChart).exists()).toBe(true);
});
 
it('includes a default group id', () => {
expect(panelType.vm.groupId).toBe('panel-type-chart');
expect(wrapper.vm.groupId).toBe('panel-type-chart');
});
});
 
describe('Anomaly Chart panel type', () => {
beforeEach(done => {
panelType.setProps({
beforeEach(() => {
wrapper.setProps({
graphData: anomalyMockGraphData,
});
panelType.vm.$nextTick(done);
return wrapper.vm.$nextTick();
});
 
it('is rendered with an anomaly chart', () => {
expect(panelType.find(AnomalyChart).isVueInstance()).toBe(true);
expect(panelType.find(AnomalyChart).exists()).toBe(true);
expect(wrapper.find(AnomalyChart).isVueInstance()).toBe(true);
expect(wrapper.find(AnomalyChart).exists()).toBe(true);
});
});
});
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(() => {
createWrapper({
graphData: graphDataPrometheusQueryRange,
});
return wrapper.vm.$nextTick();
});
it('is not present by default', () =>
wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(false);
}));
it('is not present if a time range is not set', () => {
state.logsPath = mockLogsPath;
state.timeRange = null;
return wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(false);
});
});
it('is not present if the logs path is default', () => {
state.logsPath = invalidUrl;
state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(false);
});
});
it('is not present if the logs path is not set', () => {
state.logsPath = null;
state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => {
expect(findViewLogsLink().exists()).toBe(false);
});
});
it('is present when logs path and time a range is present', () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
return wrapper.vm.$nextTick(() => {
const href = `${mockLogsPath}?duration_seconds=${mockTimeRange.duration.seconds}`;
expect(findViewLogsLink().attributes('href')).toMatch(href);
});
});
it('it is overriden when a datazoom event is received', () => {
state.logsPath = mockLogsPath;
state.timeRange = mockTimeRange;
const zoomedTimeRange = {
start: '2020-01-01T00:00:00.000Z',
end: '2020-01-01T01:00:00.000Z',
};
findTimeChart().vm.$emit('datazoom', zoomedTimeRange);
return wrapper.vm.$nextTick(() => {
const start = encodeURIComponent(zoomedTimeRange.start);
const end = encodeURIComponent(zoomedTimeRange.end);
expect(findViewLogsLink().attributes('href')).toMatch(
`${mockLogsPath}?start=${start}&end=${end}`,
);
});
});
});
Loading
Loading
@@ -119,20 +199,18 @@ describe('Panel Type component', () => {
const clipboardText = 'A value to copy.';
 
beforeEach(() => {
store = createStore();
panelType = createWrapper({
createWrapper({
clipboardText,
dashboardWidth,
graphData: graphDataPrometheusQueryRange,
});
});
 
afterEach(() => {
panelType.destroy();
wrapper.destroy();
});
 
it('sets clipboard text on the dropdown', () => {
const link = () => panelType.find('.js-chart-link');
const link = () => wrapper.find('.js-chart-link');
 
expect(link().exists()).toBe(true);
expect(link().element.dataset.clipboardText).toBe(clipboardText);
Loading
Loading
@@ -140,22 +218,20 @@ describe('Panel Type component', () => {
});
 
describe('when downloading metrics data as CSV', () => {
beforeEach(done => {
beforeEach(() => {
graphDataPrometheusQueryRange.y_label = 'metric';
store = createStore();
panelType = shallowMount(PanelType, {
wrapper = shallowMount(PanelType, {
propsData: {
clipboardText: exampleText,
dashboardWidth,
graphData: graphDataPrometheusQueryRange,
},
store,
});
panelType.vm.$nextTick(done);
return wrapper.vm.$nextTick();
});
 
afterEach(() => {
panelType.destroy();
wrapper.destroy();
});
 
describe('csvText', () => {
Loading
Loading
@@ -165,7 +241,7 @@ describe('Panel Type component', () => {
const firstRow = `${data[0][0]},${data[0][1]}`;
const secondRow = `${data[1][0]},${data[1][1]}`;
 
expect(panelType.vm.csvText).toBe(`${header}\r\n${firstRow}\r\n${secondRow}\r\n`);
expect(wrapper.vm.csvText).toBe(`${header}\r\n${firstRow}\r\n${secondRow}\r\n`);
});
});
 
Loading
Loading
@@ -174,7 +250,7 @@ describe('Panel Type component', () => {
expect(global.URL.createObjectURL).toHaveBeenLastCalledWith(expect.any(Blob));
expect(global.URL.createObjectURL).toHaveBeenLastCalledWith(
expect.objectContaining({
size: panelType.vm.csvText.length,
size: wrapper.vm.csvText.length,
type: 'text/plain',
}),
);
Loading
Loading
Loading
Loading
@@ -90,6 +90,16 @@ describe('Monitoring mutations', () => {
expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json');
expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-foss');
});
it('should not remove default value of logsPath', () => {
const defaultLogsPath = stateCopy.logsPath;
mutations[types.SET_ENDPOINTS](stateCopy, {
dashboardEndpoint: 'dashboard.json',
});
expect(stateCopy.logsPath).toBe(defaultLogsPath);
});
});
describe('Individual panel/metric results', () => {
const metricId = '12_system_metrics_kubernetes_container_memory_total';
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BackgroundMigration::FixProjectsWithoutProjectFeature, :migration, schema: 2020_01_27_111840 do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:project_features) { table(:project_features) }
let(:namespace) { namespaces.create(name: 'foo', path: 'foo') }
let!(:project) { projects.create!(namespace_id: namespace.id) }
let(:private_project_without_feature) { projects.create!(namespace_id: namespace.id, visibility_level: 0) }
let(:public_project_without_feature) { projects.create!(namespace_id: namespace.id, visibility_level: 20) }
let!(:projects_without_feature) { [private_project_without_feature, public_project_without_feature] }
before do
project_features.create({ project_id: project.id, pages_access_level: 20 })
end
subject { described_class.new.perform(Project.minimum(:id), Project.maximum(:id)) }
def project_feature_records
project_features.order(:project_id).pluck(:project_id)
end
def features(project)
project_features.find_by(project_id: project.id)&.attributes
end
it 'creates a ProjectFeature for projects without it' do
expect { subject }.to change { project_feature_records }.from([project.id]).to([project.id, *projects_without_feature.map(&:id)])
end
it 'creates ProjectFeature records with default values for a public project' do
subject
expect(features(public_project_without_feature)).to include(
{
"merge_requests_access_level" => 20,
"issues_access_level" => 20,
"wiki_access_level" => 20,
"snippets_access_level" => 20,
"builds_access_level" => 20,
"repository_access_level" => 20,
"pages_access_level" => 20,
"forking_access_level" => 20
}
)
end
it 'creates ProjectFeature records with default values for a private project' do
subject
expect(features(private_project_without_feature)).to include("pages_access_level" => 10)
end
context 'when access control to pages is forced' do
before do
allow(::Gitlab::Pages).to receive(:access_control_is_forced?).and_return(true)
end
it 'creates ProjectFeature records with default values for a public project' do
subject
expect(features(public_project_without_feature)).to include("pages_access_level" => 10)
end
end
it 'sets created_at/updated_at timestamps' do
subject
expect(project_features.where('created_at IS NULL OR updated_at IS NULL')).to be_empty
end
end
Loading
Loading
@@ -10,7 +10,7 @@ describe Gitlab::Graphql::Connections::Keyset::Conditions::NotNullCondition do
 
context 'when there is only one ordering field' do
let(:arel_table) { Issue.arel_table }
let(:order_list) { ['id'] }
let(:order_list) { [double(named_function: nil, attribute_name: 'id')] }
let(:values) { [500] }
let(:operators) { ['>'] }
 
Loading
Loading
@@ -25,7 +25,7 @@ describe Gitlab::Graphql::Connections::Keyset::Conditions::NotNullCondition do
 
context 'when ordering by a column attribute' do
let(:arel_table) { Issue.arel_table }
let(:order_list) { %w(relative_position id) }
let(:order_list) { [double(named_function: nil, attribute_name: 'relative_position'), double(named_function: nil, attribute_name: 'id')] }
let(:values) { [1500, 500] }
 
shared_examples ':after condition' do
Loading
Loading
@@ -71,5 +71,45 @@ describe Gitlab::Graphql::Connections::Keyset::Conditions::NotNullCondition do
it_behaves_like ':after condition'
end
end
context 'when ordering by LOWER' do
let(:arel_table) { Project.arel_table }
let(:relation) { Project.order(arel_table['name'].lower.asc).order(:id) }
let(:order_list) { Gitlab::Graphql::Connections::Keyset::OrderInfo.build_order_list(relation) }
let(:values) { ['Test', 500] }
context 'when :after' do
it 'generates :after sql' do
expected_sql = <<~SQL
(LOWER("projects"."name") > 'test')
OR (
LOWER("projects"."name") = 'test'
AND
"projects"."id" > 500
)
OR (LOWER("projects"."name") IS NULL)
SQL
expect(condition.build.squish).to eq expected_sql.squish
end
end
context 'when :before' do
let(:before_or_after) { :before }
it 'generates :before sql' do
expected_sql = <<~SQL
(LOWER("projects"."name") > 'test')
OR (
LOWER("projects"."name") = 'test'
AND
"projects"."id" > 500
)
SQL
expect(condition.build.squish).to eq expected_sql.squish
end
end
end
end
end
Loading
Loading
@@ -11,7 +11,7 @@ describe Gitlab::Graphql::Connections::Keyset::Conditions::NullCondition do
 
context 'when ordering by a column attribute' do
let(:arel_table) { Issue.arel_table }
let(:order_list) { %w(relative_position id) }
let(:order_list) { [double(named_function: nil, attribute_name: 'relative_position'), double(named_function: nil, attribute_name: 'id')] }
 
shared_examples ':after condition' do
it 'generates sql' do
Loading
Loading
@@ -54,5 +54,42 @@ describe Gitlab::Graphql::Connections::Keyset::Conditions::NullCondition do
it_behaves_like ':after condition'
end
end
context 'when ordering by LOWER' do
let(:arel_table) { Project.arel_table }
let(:relation) { Project.order(arel_table['name'].lower.asc).order(:id) }
let(:order_list) { Gitlab::Graphql::Connections::Keyset::OrderInfo.build_order_list(relation) }
context 'when :after' do
it 'generates sql' do
expected_sql = <<~SQL
(
LOWER("projects"."name") IS NULL
AND
"projects"."id" > 500
)
SQL
expect(condition.build.squish).to eq expected_sql.squish
end
end
context 'when :before' do
let(:before_or_after) { :before }
it 'generates :before sql' do
expected_sql = <<~SQL
(
LOWER("projects"."name") IS NULL
AND
"projects"."id" > 500
)
OR (LOWER("projects"."name") IS NOT NULL)
SQL
expect(condition.build.squish).to eq expected_sql.squish
end
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment