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

Add latest changes from gitlab-org/gitlab@master

parent 02ab65d4
No related branches found
No related tags found
No related merge requests found
Showing
with 562 additions and 318 deletions
Loading
Loading
@@ -8,7 +8,6 @@ describe 'Merge request > User toggles whitespace changes', :js do
let(:user) { project.creator }
 
before do
stub_feature_flags(single_mr_diff_view: false)
project.add_maintainer(user)
sign_in(user)
visit diffs_project_merge_request_path(project, merge_request)
Loading
Loading
@@ -16,8 +15,6 @@ describe 'Merge request > User toggles whitespace changes', :js do
find('.js-show-diff-settings').click
end
 
it_behaves_like 'rendering a single diff version'
it 'has a button to toggle whitespace changes' do
expect(page).to have_content 'Show whitespace changes'
end
Loading
Loading
Loading
Loading
@@ -9,7 +9,6 @@ describe 'User views diffs', :js do
let(:project) { create(:project, :public, :repository) }
 
before do
stub_feature_flags(single_mr_diff_view: false)
stub_feature_flags(diffs_batch_load: false)
visit(diffs_project_merge_request_path(project, merge_request))
 
Loading
Loading
@@ -18,8 +17,6 @@ describe 'User views diffs', :js do
find('.js-toggle-tree-list').click
end
 
it_behaves_like 'rendering a single diff version'
shared_examples 'unfold diffs' do
it 'unfolds diffs upwards' do
first('.js-unfold').click
Loading
Loading
Loading
Loading
@@ -12,11 +12,9 @@ describe 'Editing file blob', :js do
let(:readme_file_path) { 'README.md' }
 
before do
stub_feature_flags(web_ide_default: false, single_mr_diff_view: false)
stub_feature_flags(web_ide_default: false)
end
 
it_behaves_like 'rendering a single diff version'
context 'as a developer' do
let(:user) { create(:user) }
let(:role) { :developer }
Loading
Loading
Loading
Loading
@@ -9,14 +9,11 @@ describe 'View on environment', :js do
let(:user) { project.creator }
 
before do
stub_feature_flags(single_mr_diff_view: false)
stub_feature_flags(diffs_batch_load: false)
 
project.add_maintainer(user)
end
 
it_behaves_like 'rendering a single diff version'
context 'when the branch has a route map' do
let(:route_map) do
<<-MAP.strip_heredoc
Loading
Loading
Loading
Loading
@@ -65,22 +65,6 @@ describe 'Triggers', :js do
expect(page.find('.triggers-list')).to have_content new_trigger_title
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
end
it 'edit "legacy" trigger and save' do
# Create new trigger without owner association, i.e. Legacy trigger
create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil)
visit project_settings_ci_cd_path(@project)
# See if the trigger can be edited and description is blank
find('a[title="Edit"]').send_keys(:return)
expect(page.find('#trigger_description').value).to have_content ''
# See if trigger can be updated with description and saved successfully
fill_in 'trigger_description', with: new_trigger_title
click_button 'Save trigger'
expect(page.find('.flash-notice')).to have_content 'Trigger was successfully updated.'
expect(page.find('.triggers-list')).to have_content new_trigger_title
end
end
 
describe 'trigger "Revoke" workflow' do
Loading
Loading
@@ -106,43 +90,18 @@ describe 'Triggers', :js do
end
 
describe 'show triggers workflow' do
before do
stub_feature_flags(use_legacy_pipeline_triggers: false)
end
it 'contains trigger description placeholder' do
expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description'
end
 
it 'show "invalid" badge for legacy trigger' do
create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil)
visit project_settings_ci_cd_path(@project)
expect(page.find('.triggers-list')).to have_content 'invalid'
end
it 'show "invalid" badge for trigger with owner having insufficient permissions' do
create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
 
# See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable
expect(page.find('.triggers-list')).to have_content 'invalid'
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
 
it 'do not show "Edit" or full token for legacy trigger' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
.update_attribute(:owner, nil)
visit project_settings_ci_cd_path(@project)
# See if trigger not owned shows only first few token chars and doesn't have copy-to-clipboard button
expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
# See if trigger is non-editable
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
it 'do not show "Edit" or full token for not owned trigger' do
# Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
Loading
Loading
@@ -169,56 +128,5 @@ describe 'Triggers', :js do
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
end
context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
before do
stub_feature_flags(use_legacy_pipeline_triggers: true)
end
it 'show "legacy" badge for legacy trigger' do
create(:ci_trigger, owner: nil, project: @project)
visit project_settings_ci_cd_path(@project)
# See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
expect(page.find('.triggers-list')).to have_content 'legacy'
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
end
it 'show "invalid" badge for trigger with owner having insufficient permissions' do
create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
# See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable
expect(page.find('.triggers-list')).to have_content 'invalid'
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
it 'do not show "Edit" or full token for not owned trigger' do
# Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
# See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button
expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
# See if trigger owner name doesn't match with current_user and trigger is non-editable
expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
it 'show "Edit" and full token for owned trigger' do
create(:ci_trigger, owner: user, project: @project, description: trigger_title)
visit project_settings_ci_cd_path(@project)
# See if trigger shows full token and has copy-to-clipboard button
expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard')
# See if trigger owner name matches with current_user and is editable
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
end
end
end
end
Loading
Loading
@@ -175,6 +175,67 @@
}
}
]
},
{
"id": 3,
"title": "Issue with Epic",
"author_id": 1,
"project_id": 8,
"created_at": "2019-12-08T19:41:11.233Z",
"updated_at": "2019-12-08T19:41:53.194Z",
"position": 0,
"branch_name": null,
"description": "Donec at nulla vitae sem molestie rutrum ut at sem.",
"state": "opened",
"iid": 3,
"updated_by_id": null,
"confidential": false,
"due_date": null,
"moved_to_id": null,
"issue_assignees": [],
"notes": [],
"milestone": {
"id": 2,
"title": "A group milestone",
"description": "Group-level milestone",
"due_date": null,
"created_at": "2016-06-14T15:02:04.415Z",
"updated_at": "2016-06-14T15:02:04.415Z",
"state": "active",
"iid": 1,
"group_id": 100
},
"epic": {
"id": 1,
"group_id": 5,
"author_id": 1,
"assignee_id": null,
"iid": 1,
"updated_by_id": null,
"last_edited_by_id": null,
"lock_version": 0,
"start_date": null,
"end_date": null,
"last_edited_at": null,
"created_at": "2019-12-08T19:37:07.098Z",
"updated_at": "2019-12-08T19:43:11.568Z",
"title": "An epic",
"description": null,
"start_date_sourcing_milestone_id": null,
"due_date_sourcing_milestone_id": null,
"start_date_fixed": null,
"due_date_fixed": null,
"start_date_is_fixed": null,
"due_date_is_fixed": null,
"closed_by_id": null,
"closed_at": null,
"parent_id": null,
"relative_position": null,
"state_id": "opened",
"start_date_sourcing_epic_id": null,
"due_date_sourcing_epic_id": null,
"milestone_id": null
}
}
],
"snippets": [
Loading
Loading
Loading
Loading
@@ -13,6 +13,7 @@ describe('ErrorDetails', () => {
let wrapper;
let actions;
let getters;
let mocks;
 
const findInput = name => {
const inputs = wrapper.findAll(GlFormInput).filter(c => c.attributes('name') === name);
Loading
Loading
@@ -24,13 +25,27 @@ describe('ErrorDetails', () => {
stubs: { LoadingButton },
localVue,
store,
mocks,
propsData: {
issueId: '123',
projectPath: '/root/gitlab-test',
issueDetailsPath: '/123/details',
issueStackTracePath: '/stacktrace',
projectIssuesPath: '/test-project/issues/',
csrfToken: 'fakeToken',
},
});
wrapper.setData({
GQLerror: {
id: 129381,
title: 'Issue title',
externalUrl: 'http://sentry.gitlab.net/gitlab',
firstSeen: '2017-05-26T13:32:48Z',
lastSeen: '2018-05-26T13:32:48Z',
count: 12,
userCount: 2,
},
});
}
 
beforeEach(() => {
Loading
Loading
@@ -61,6 +76,19 @@ describe('ErrorDetails', () => {
},
},
});
const query = jest.fn();
mocks = {
$apollo: {
query,
queries: {
GQLerror: {
loading: true,
stopPolling: jest.fn(),
},
},
},
};
});
 
afterEach(() => {
Loading
Loading
@@ -85,10 +113,11 @@ describe('ErrorDetails', () => {
beforeEach(() => {
store.state.details.loading = false;
store.state.details.error.id = 1;
mocks.$apollo.queries.GQLerror.loading = false;
mountComponent();
});
 
it('should show Sentry error details without stacktrace', () => {
mountComponent();
expect(wrapper.find(GlLink).exists()).toBe(true);
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(Stacktrace).exists()).toBe(false);
Loading
Loading
@@ -99,13 +128,17 @@ describe('ErrorDetails', () => {
it('should show language and error level badges', () => {
store.state.details.error.tags = { level: 'error', logger: 'ruby' };
mountComponent();
expect(wrapper.findAll(GlBadge).length).toBe(2);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(GlBadge).length).toBe(2);
});
});
 
it('should NOT show the badge if the tag is not present', () => {
store.state.details.error.tags = { level: 'error' };
mountComponent();
expect(wrapper.findAll(GlBadge).length).toBe(1);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.findAll(GlBadge).length).toBe(1);
});
});
});
 
Loading
Loading
@@ -113,8 +146,10 @@ describe('ErrorDetails', () => {
it('should show stacktrace', () => {
store.state.details.loadingStacktrace = false;
mountComponent();
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(Stacktrace).exists()).toBe(true);
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find(Stacktrace).exists()).toBe(true);
});
});
 
it('should NOT show stacktrace if no entries', () => {
Loading
Loading
@@ -128,15 +163,6 @@ describe('ErrorDetails', () => {
 
describe('When a user clicks the create issue button', () => {
beforeEach(() => {
store.state.details.error = {
id: 129381,
title: 'Issue title',
external_url: 'http://sentry.gitlab.net/gitlab',
first_seen: '2017-05-26T13:32:48Z',
last_seen: '2018-05-26T13:32:48Z',
count: 12,
user_count: 2,
};
mountComponent();
});
 
Loading
Loading
import * as diffsHelper from '~/helpers/diffs_helper';
describe('diffs helper', () => {
function getDiffFile(withOverrides = {}) {
return {
parallel_diff_lines: ['line'],
highlighted_diff_lines: ['line'],
blob: {
readable_text: 'text',
},
...withOverrides,
};
}
describe('hasInlineLines', () => {
it('is false when the file does not exist', () => {
expect(diffsHelper.hasInlineLines()).toBeFalsy();
});
it('is false when the file does not have the highlighted_diff_lines property', () => {
const missingInline = getDiffFile({ highlighted_diff_lines: undefined });
expect(diffsHelper.hasInlineLines(missingInline)).toBeFalsy();
});
it('is false when the file has zero highlighted_diff_lines', () => {
const emptyInline = getDiffFile({ highlighted_diff_lines: [] });
expect(diffsHelper.hasInlineLines(emptyInline)).toBeFalsy();
});
it('is true when the file has at least 1 highlighted_diff_lines', () => {
expect(diffsHelper.hasInlineLines(getDiffFile())).toBeTruthy();
});
});
describe('hasParallelLines', () => {
it('is false when the file does not exist', () => {
expect(diffsHelper.hasParallelLines()).toBeFalsy();
});
it('is false when the file does not have the parallel_diff_lines property', () => {
const missingInline = getDiffFile({ parallel_diff_lines: undefined });
expect(diffsHelper.hasParallelLines(missingInline)).toBeFalsy();
});
it('is false when the file has zero parallel_diff_lines', () => {
const emptyInline = getDiffFile({ parallel_diff_lines: [] });
expect(diffsHelper.hasParallelLines(emptyInline)).toBeFalsy();
});
it('is true when the file has at least 1 parallel_diff_lines', () => {
expect(diffsHelper.hasParallelLines(getDiffFile())).toBeTruthy();
});
});
describe('isSingleViewStyle', () => {
it('is true when the file has at least 1 inline line but no parallel lines for any reason', () => {
const noParallelLines = getDiffFile({ parallel_diff_lines: undefined });
const emptyParallelLines = getDiffFile({ parallel_diff_lines: [] });
expect(diffsHelper.isSingleViewStyle(noParallelLines)).toBeTruthy();
expect(diffsHelper.isSingleViewStyle(emptyParallelLines)).toBeTruthy();
});
it('is true when the file has at least 1 parallel line but no inline lines for any reason', () => {
const noInlineLines = getDiffFile({ highlighted_diff_lines: undefined });
const emptyInlineLines = getDiffFile({ highlighted_diff_lines: [] });
expect(diffsHelper.isSingleViewStyle(noInlineLines)).toBeTruthy();
expect(diffsHelper.isSingleViewStyle(emptyInlineLines)).toBeTruthy();
});
it('is true when the file does not have any inline lines or parallel lines for any reason', () => {
const noLines = getDiffFile({
highlighted_diff_lines: undefined,
parallel_diff_lines: undefined,
});
const emptyLines = getDiffFile({
highlighted_diff_lines: [],
parallel_diff_lines: [],
});
expect(diffsHelper.isSingleViewStyle(noLines)).toBeTruthy();
expect(diffsHelper.isSingleViewStyle(emptyLines)).toBeTruthy();
expect(diffsHelper.isSingleViewStyle()).toBeTruthy();
});
it('is false when the file has both inline and parallel lines', () => {
expect(diffsHelper.isSingleViewStyle(getDiffFile())).toBeFalsy();
});
});
describe.each`
context | inline | parallel | blob | expected
${'only has inline lines'} | ${['line']} | ${undefined} | ${undefined} | ${true}
${'only has parallel lines'} | ${undefined} | ${['line']} | ${undefined} | ${true}
${"doesn't have inline, parallel, or blob"} | ${undefined} | ${undefined} | ${undefined} | ${true}
${'has blob readable text'} | ${undefined} | ${undefined} | ${{ readable_text: 'text' }} | ${false}
`('when hasDiff', ({ context, inline, parallel, blob, expected }) => {
it(`${context}`, () => {
const diffFile = getDiffFile({
highlighted_diff_lines: inline,
parallel_diff_lines: parallel,
blob,
});
expect(diffsHelper.hasDiff(diffFile)).toEqual(expected);
});
});
});
Loading
Loading
@@ -345,7 +345,7 @@ describe('Dashboard', () => {
 
it('metrics can be swapped', done => {
const firstDraggable = findDraggables().at(0);
const mockMetrics = [...metricsGroupsAPIResponse[1].panels];
const mockMetrics = [...metricsGroupsAPIResponse.panel_groups[1].panels];
 
const firstTitle = mockMetrics[0].title;
const secondTitle = mockMetrics[1].title;
Loading
Loading
Loading
Loading
@@ -331,77 +331,80 @@ export const mockedQueryResultPayloadCoresTotal = {
],
};
 
export const metricsGroupsAPIResponse = [
{
group: 'Response metrics (NGINX Ingress VTS)',
priority: 10,
panels: [
{
metrics: [
{
id: 'response_metrics_nginx_ingress_throughput_status_code',
label: 'Status Code',
metric_id: 1,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29',
query_range:
'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)',
unit: 'req / sec',
},
],
title: 'Throughput',
type: 'area-chart',
weight: 1,
y_label: 'Requests / Sec',
},
],
},
{
group: 'System metrics (Kubernetes)',
priority: 5,
panels: [
{
title: 'Memory Usage (Pod average)',
type: 'area-chart',
y_label: 'Memory Used per Pod',
weight: 2,
metrics: [
{
id: 'system_metrics_kubernetes_container_memory_average',
query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024',
label: 'Pod average',
unit: 'MB',
metric_id: 17,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024',
appearance: {
line: {
width: 2,
export const metricsGroupsAPIResponse = {
dashboard: 'Environment metrics',
panel_groups: [
{
group: 'Response metrics (NGINX Ingress VTS)',
priority: 10,
panels: [
{
metrics: [
{
id: 'response_metrics_nginx_ingress_throughput_status_code',
label: 'Status Code',
metric_id: 1,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29',
query_range:
'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) by (status_code)',
unit: 'req / sec',
},
],
title: 'Throughput',
type: 'area-chart',
weight: 1,
y_label: 'Requests / Sec',
},
],
},
{
group: 'System metrics (Kubernetes)',
priority: 5,
panels: [
{
title: 'Memory Usage (Pod average)',
type: 'area-chart',
y_label: 'Memory Used per Pod',
weight: 2,
metrics: [
{
id: 'system_metrics_kubernetes_container_memory_average',
query_range:
'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024',
label: 'Pod average',
unit: 'MB',
metric_id: 17,
prometheus_endpoint_path:
'/root/autodevops-deploy/environments/32/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+by+%28job%29%29+without+%28job%29+%2F+count%28avg%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%25%7Bci_environment_slug%7D-%28%5B%5Ec%5D.%2A%7Cc%28%5B%5Ea%5D%7Ca%28%5B%5En%5D%7Cn%28%5B%5Ea%5D%7Ca%28%5B%5Er%5D%7Cr%5B%5Ey%5D%29%29%29%29.%2A%7C%29-%28.%2A%29%22%2Cnamespace%3D%22%25%7Bkube_namespace%7D%22%7D%29+without+%28job%29%29+%2F1024%2F1024',
appearance: {
line: {
width: 2,
},
},
},
},
],
},
{
title: 'Core Usage (Total)',
type: 'area-chart',
y_label: 'Total Cores',
weight: 3,
metrics: [
{
id: 'system_metrics_kubernetes_container_cores_total',
query_range:
'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)',
label: 'Total',
unit: 'cores',
metric_id: 13,
},
],
},
],
},
];
],
},
{
title: 'Core Usage (Total)',
type: 'area-chart',
y_label: 'Total Cores',
weight: 3,
metrics: [
{
id: 'system_metrics_kubernetes_container_cores_total',
query_range:
'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)',
label: 'Total',
unit: 'cores',
metric_id: 13,
},
],
},
],
},
],
};
 
export const environmentData = [
{
Loading
Loading
Loading
Loading
@@ -298,7 +298,7 @@ describe('Monitoring store actions', () => {
);
expect(commit).toHaveBeenCalledWith(
types.RECEIVE_METRICS_DATA_SUCCESS,
metricsDashboardResponse.dashboard.panel_groups,
metricsDashboardResponse.dashboard,
);
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params);
});
Loading
Loading
@@ -441,7 +441,7 @@ describe('Monitoring store actions', () => {
beforeEach(() => {
state = storeState();
[metric] = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics;
[data] = metricsGroupsAPIResponse[0].panels[0].metrics;
[data] = metricsGroupsAPIResponse.panel_groups[0].panels[0].metrics;
});
 
it('commits result', done => {
Loading
Loading
Loading
Loading
@@ -100,12 +100,12 @@ describe('Monitoring mutations', () => {
values: [[0, 1], [1, 1], [1, 3]],
},
];
const dashboardGroups = metricsDashboardResponse.dashboard.panel_groups;
const { dashboard } = metricsDashboardResponse;
const getMetric = () => stateCopy.dashboard.panel_groups[0].panels[0].metrics[0];
 
describe('REQUEST_METRIC_RESULT', () => {
beforeEach(() => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboard);
});
it('stores a loading state on a metric', () => {
expect(stateCopy.showEmptyState).toBe(true);
Loading
Loading
@@ -128,7 +128,7 @@ describe('Monitoring mutations', () => {
 
describe('RECEIVE_METRIC_RESULT_SUCCESS', () => {
beforeEach(() => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboard);
});
it('clears empty state', () => {
expect(stateCopy.showEmptyState).toBe(true);
Loading
Loading
@@ -161,7 +161,7 @@ describe('Monitoring mutations', () => {
 
describe('RECEIVE_METRIC_RESULT_FAILURE', () => {
beforeEach(() => {
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboardGroups);
mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, dashboard);
});
it('maintains the loading state when a metric fails', () => {
expect(stateCopy.showEmptyState).toBe(true);
Loading
Loading
Loading
Loading
@@ -80,11 +80,20 @@ describe Projects::ErrorTrackingHelper do
let(:issue_id) { 1234 }
let(:route_params) { [project.owner, project, issue_id, { format: :json }] }
let(:details_path) { details_namespace_project_error_tracking_index_path(*route_params) }
let(:project_path) { project.full_path }
let(:stack_trace_path) { stack_trace_namespace_project_error_tracking_index_path(*route_params) }
let(:issues_path) { project_issues_path(project) }
 
let(:result) { helper.error_details_data(project, issue_id) }
 
it 'returns the correct issue id' do
expect(result['issue-id']).to eq issue_id
end
it 'returns the correct project path' do
expect(result['project-path']).to eq project_path
end
it 'returns the correct details path' do
expect(result['issue-details-path']).to eq details_path
end
Loading
Loading
Loading
Loading
@@ -10,6 +10,7 @@ import CompareVersions from '~/diffs/components/compare_versions.vue';
import HiddenFilesWarning from '~/diffs/components/hidden_files_warning.vue';
import CommitWidget from '~/diffs/components/commit_widget.vue';
import TreeList from '~/diffs/components/tree_list.vue';
import { INLINE_DIFF_VIEW_TYPE, PARALLEL_DIFF_VIEW_TYPE } from '~/diffs/constants';
import createDiffsStore from '../create_diffs_store';
import diffsMockData from '../mock_data/merge_request_diffs';
 
Loading
Loading
@@ -41,7 +42,6 @@ describe('diffs/components/app', () => {
changesEmptyStateIllustration: '',
dismissEndpoint: '',
showSuggestPopover: true,
useSingleDiffStyle: false,
...props,
},
store,
Loading
Loading
@@ -53,6 +53,12 @@ describe('diffs/components/app', () => {
});
}
 
function getOppositeViewType(currentViewType) {
return currentViewType === INLINE_DIFF_VIEW_TYPE
? PARALLEL_DIFF_VIEW_TYPE
: INLINE_DIFF_VIEW_TYPE;
}
beforeEach(() => {
// setup globals (needed for component to mount :/)
window.mrTabs = jasmine.createSpyObj('mrTabs', ['resetViewContainer']);
Loading
Loading
@@ -82,9 +88,146 @@ describe('diffs/components/app', () => {
spyOn(wrapper.vm, 'startRenderDiffsQueue');
spyOn(wrapper.vm, 'unwatchDiscussions');
store.state.diffs.retrievingBatches = true;
store.state.diffs.diffFiles = [];
wrapper.vm.$nextTick(done);
});
 
describe('when the diff view type changes and it should load a single diff view style', () => {
const noLinesDiff = {
highlighted_diff_lines: [],
parallel_diff_lines: [],
};
const parallelLinesDiff = {
highlighted_diff_lines: [],
parallel_diff_lines: ['line'],
};
const inlineLinesDiff = {
highlighted_diff_lines: ['line'],
parallel_diff_lines: [],
};
const fullDiff = {
highlighted_diff_lines: ['line'],
parallel_diff_lines: ['line'],
};
function expectFetchToOccur({
vueInstance,
done = () => {},
batch = false,
existingFiles = 1,
} = {}) {
vueInstance.$nextTick(() => {
expect(vueInstance.diffFiles.length).toEqual(existingFiles);
if (!batch) {
expect(vueInstance.fetchDiffFiles).toHaveBeenCalled();
expect(vueInstance.fetchDiffFilesBatch).not.toHaveBeenCalled();
} else {
expect(vueInstance.fetchDiffFiles).not.toHaveBeenCalled();
expect(vueInstance.fetchDiffFilesBatch).toHaveBeenCalled();
}
done();
});
}
beforeEach(() => {
wrapper.vm.glFeatures.singleMrDiffView = true;
});
it('fetches diffs if it has none', done => {
wrapper.vm.isLatestVersion = () => false;
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expectFetchToOccur({ vueInstance: wrapper.vm, batch: false, existingFiles: 0, done });
});
it('fetches diffs if it has both view styles, but no lines in either', done => {
wrapper.vm.isLatestVersion = () => false;
store.state.diffs.diffFiles.push(noLinesDiff);
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expectFetchToOccur({ vueInstance: wrapper.vm, done });
});
it('fetches diffs if it only has inline view style', done => {
wrapper.vm.isLatestVersion = () => false;
store.state.diffs.diffFiles.push(inlineLinesDiff);
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expectFetchToOccur({ vueInstance: wrapper.vm, done });
});
it('fetches diffs if it only has parallel view style', done => {
wrapper.vm.isLatestVersion = () => false;
store.state.diffs.diffFiles.push(parallelLinesDiff);
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expectFetchToOccur({ vueInstance: wrapper.vm, done });
});
it('fetches batch diffs if it has none', done => {
wrapper.vm.glFeatures.diffsBatchLoad = true;
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, existingFiles: 0, done });
});
it('fetches batch diffs if it has both view styles, but no lines in either', done => {
wrapper.vm.glFeatures.diffsBatchLoad = true;
store.state.diffs.diffFiles.push(noLinesDiff);
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, done });
});
it('fetches batch diffs if it only has inline view style', done => {
wrapper.vm.glFeatures.diffsBatchLoad = true;
store.state.diffs.diffFiles.push(inlineLinesDiff);
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, done });
});
it('fetches batch diffs if it only has parallel view style', done => {
wrapper.vm.glFeatures.diffsBatchLoad = true;
store.state.diffs.diffFiles.push(parallelLinesDiff);
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expectFetchToOccur({ vueInstance: wrapper.vm, batch: true, done });
});
it('does not fetch diffs if it has already fetched both styles of diff', () => {
wrapper.vm.glFeatures.diffsBatchLoad = false;
store.state.diffs.diffFiles.push(fullDiff);
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expect(wrapper.vm.diffFiles.length).toEqual(1);
expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled();
expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled();
});
it('does not fetch batch diffs if it has already fetched both styles of diff', () => {
wrapper.vm.glFeatures.diffsBatchLoad = true;
store.state.diffs.diffFiles.push(fullDiff);
store.state.diffs.diffViewType = getOppositeViewType(wrapper.vm.diffViewType);
expect(wrapper.vm.diffFiles.length).toEqual(1);
expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled();
expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled();
});
});
it('calls fetchDiffFiles if diffsBatchLoad is not enabled', done => {
wrapper.vm.glFeatures.diffsBatchLoad = false;
wrapper.vm.fetchData(false);
Loading
Loading
Loading
Loading
@@ -120,7 +120,7 @@ describe('DiffsStoreActions', () => {
 
describe('fetchDiffFiles', () => {
it('should fetch diff files', done => {
const endpoint = '/fetch/diff/files?w=1';
const endpoint = '/fetch/diff/files?view=inline&w=1';
const mock = new MockAdapter(axios);
const res = { diff_files: 1, merge_request_diffs: [] };
mock.onGet(endpoint).reply(200, res);
Loading
Loading
@@ -128,7 +128,7 @@ describe('DiffsStoreActions', () => {
testAction(
fetchDiffFiles,
{},
{ endpoint },
{ endpoint, diffFiles: [], showWhitespace: false, diffViewType: 'inline' },
[
{ type: types.SET_LOADING, payload: true },
{ type: types.SET_LOADING, payload: false },
Loading
Loading
Loading
Loading
@@ -52,7 +52,14 @@ describe('DiffsStoreMutations', () => {
 
describe('SET_DIFF_DATA', () => {
it('should set diff data type properly', () => {
const state = {};
const state = {
diffFiles: [
{
content_sha: diffFileMockData.content_sha,
file_hash: diffFileMockData.file_hash,
},
],
};
const diffMock = {
diff_files: [diffFileMockData],
};
Loading
Loading
@@ -62,9 +69,41 @@ describe('DiffsStoreMutations', () => {
const firstLine = state.diffFiles[0].parallel_diff_lines[0];
 
expect(firstLine.right.text).toBeUndefined();
expect(state.diffFiles.length).toEqual(1);
expect(state.diffFiles[0].renderIt).toEqual(true);
expect(state.diffFiles[0].collapsed).toEqual(false);
});
describe('given diffsBatchLoad feature flag is enabled', () => {
beforeEach(() => {
gon.features = { diffsBatchLoad: true };
});
afterEach(() => {
delete gon.features;
});
it('should not modify the existing state', () => {
const state = {
diffFiles: [
{
content_sha: diffFileMockData.content_sha,
file_hash: diffFileMockData.file_hash,
highlighted_diff_lines: [],
},
],
};
const diffMock = {
diff_files: [diffFileMockData],
};
mutations[types.SET_DIFF_DATA](state, diffMock);
// If the batch load is enabled, there shouldn't be any processing
// done on the existing state object, so we shouldn't have this.
expect(state.diffFiles[0].parallel_diff_lines).toBeUndefined();
});
});
});
 
describe('SET_DIFFSET_DIFF_DATA_BATCH_DATA', () => {
Loading
Loading
@@ -168,11 +207,17 @@ describe('DiffsStoreMutations', () => {
it('should update the state with the given data for the given file hash', () => {
const fileHash = 123;
const state = {
diffFiles: [{}, { file_hash: fileHash, existing_field: 0 }],
diffFiles: [{}, { content_sha: 'abc', file_hash: fileHash, existing_field: 0 }],
};
const data = {
diff_files: [
{ file_hash: fileHash, extra_field: 1, existing_field: 1, viewer: { name: 'text' } },
{
content_sha: 'abc',
file_hash: fileHash,
extra_field: 1,
existing_field: 1,
viewer: { name: 'text' },
},
],
};
 
Loading
Loading
@@ -208,7 +253,7 @@ describe('DiffsStoreMutations', () => {
discussions: [],
},
right: {
line_code: 'ABC_1',
line_code: 'ABC_2',
discussions: [],
},
},
Loading
Loading
@@ -274,7 +319,7 @@ describe('DiffsStoreMutations', () => {
discussions: [],
},
right: {
line_code: 'ABC_1',
line_code: 'ABC_2',
discussions: [],
},
},
Loading
Loading
@@ -352,7 +397,7 @@ describe('DiffsStoreMutations', () => {
discussions: [],
},
right: {
line_code: 'ABC_1',
line_code: 'ABC_2',
discussions: [],
},
},
Loading
Loading
@@ -448,6 +493,7 @@ describe('DiffsStoreMutations', () => {
discussions: [],
},
],
parallel_diff_lines: [],
},
],
};
Loading
Loading
Loading
Loading
@@ -314,11 +314,29 @@ describe('DiffsStoreUtils', () => {
});
 
describe('prepareDiffData', () => {
let mock;
let preparedDiff;
let splitInlineDiff;
let splitParallelDiff;
let completedDiff;
 
beforeEach(() => {
preparedDiff = { diff_files: [getDiffFileMock()] };
mock = getDiffFileMock();
preparedDiff = { diff_files: [mock] };
splitInlineDiff = {
diff_files: [Object.assign({}, mock, { parallel_diff_lines: undefined })],
};
splitParallelDiff = {
diff_files: [Object.assign({}, mock, { highlighted_diff_lines: undefined })],
};
completedDiff = {
diff_files: [Object.assign({}, mock, { highlighted_diff_lines: undefined })],
};
utils.prepareDiffData(preparedDiff);
utils.prepareDiffData(splitInlineDiff);
utils.prepareDiffData(splitParallelDiff);
utils.prepareDiffData(completedDiff, [mock]);
});
 
it('sets the renderIt and collapsed attribute on files', () => {
Loading
Loading
@@ -359,6 +377,19 @@ describe('DiffsStoreUtils', () => {
 
expect(firstLine.line_code).toEqual(firstLine.right.line_code);
});
it('guarantees an empty array for both diff styles', () => {
expect(splitInlineDiff.diff_files[0].parallel_diff_lines.length).toEqual(0);
expect(splitInlineDiff.diff_files[0].highlighted_diff_lines.length).toBeGreaterThan(0);
expect(splitParallelDiff.diff_files[0].parallel_diff_lines.length).toBeGreaterThan(0);
expect(splitParallelDiff.diff_files[0].highlighted_diff_lines.length).toEqual(0);
});
it('merges existing diff files with newly loaded diff files to ensure split diffs are eventually completed', () => {
expect(completedDiff.diff_files.length).toEqual(1);
expect(completedDiff.diff_files[0].parallel_diff_lines.length).toBeGreaterThan(0);
expect(completedDiff.diff_files[0].highlighted_diff_lines.length).toBeGreaterThan(0);
});
});
 
describe('isDiscussionApplicableToLine', () => {
Loading
Loading
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::BackgroundMigration::ActivatePrometheusServicesForSharedClusterApplications, :migration, schema: 2019_12_20_102807 do
include MigrationHelpers::PrometheusServiceHelpers
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:services) { table(:services) }
let(:namespace) { namespaces.create(name: 'user', path: 'user') }
let(:project) { projects.create(namespace_id: namespace.id) }
let(:columns) do
%w(project_id active properties type template push_events
issues_events merge_requests_events tag_push_events
note_events category default wiki_page_events pipeline_events
confidential_issues_events commit_events job_events
confidential_note_events deployment_events)
end
describe '#perform' do
it 'is idempotent' do
expect { subject.perform(project.id) }.to change { services.order(:id).map { |row| row.attributes } }
expect { subject.perform(project.id) }.not_to change { services.order(:id).map { |row| row.attributes } }
end
context 'non prometheus services' do
it 'does not change them' do
other_type = 'SomeOtherService'
services.create(service_params_for(project.id, active: true, type: other_type))
expect { subject.perform(project.id) }.not_to change { services.where(type: other_type).order(:id).map { |row| row.attributes } }
end
end
context 'prometheus services are configured manually ' do
it 'does not change them' do
properties = '{"api_url":"http://test.dev","manual_configuration":"1"}'
services.create(service_params_for(project.id, properties: properties, active: false))
expect { subject.perform(project.id) }.not_to change { services.order(:id).map { |row| row.attributes } }
end
end
context 'prometheus integration services do not exist' do
it 'creates missing services entries' do
subject.perform(project.id)
rows = services.order(:id).map { |row| row.attributes.slice(*columns).symbolize_keys }
expect([service_params_for(project.id, active: true)]).to eq rows
end
end
context 'prometheus integration services exist' do
context 'in active state' do
it 'does not change them' do
services.create(service_params_for(project.id, active: true))
expect { subject.perform(project.id) }.not_to change { services.order(:id).map { |row| row.attributes } }
end
end
context 'not in active state' do
it 'sets active attribute to true' do
service = services.create(service_params_for(project.id))
expect { subject.perform(project.id) }.to change { service.reload.active? }.from(false).to(true)
end
end
end
end
end
Loading
Loading
@@ -76,45 +76,8 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
end
end
 
context 'when pipeline triggered by legacy trigger' do
let(:user) { nil }
let(:trigger_request) do
build_stubbed(:ci_trigger_request, trigger: build_stubbed(:ci_trigger, owner: nil))
end
context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
before do
stub_feature_flags(use_legacy_pipeline_triggers: true)
step.perform!
end
it 'allows legacy triggers to create a pipeline' do
expect(pipeline).to be_valid
end
it 'does not break the chain' do
expect(step.break?).to eq false
end
end
context 'when :use_legacy_pipeline_triggers feature flag is disabled' do
before do
stub_feature_flags(use_legacy_pipeline_triggers: false)
step.perform!
end
it 'prevents legacy triggers from creating a pipeline' do
expect(pipeline.errors.to_a).to include /Trigger token is invalid/
end
it 'breaks the pipeline builder chain' do
expect(step.break?).to eq true
end
end
end
describe '#allowed_to_create?' do
subject { step.allowed_to_create? }
describe '#allowed_to_write_ref?' do
subject { step.send(:allowed_to_write_ref?) }
 
context 'when user is a developer' do
before do
Loading
Loading
Loading
Loading
@@ -578,3 +578,30 @@ zoom_meetings:
sentry_issue:
- issue
design_versions: *version
epic:
- subscriptions
- award_emoji
- description_versions
- author
- assignee
- issues
- epic_issues
- milestone
- notes
- label_links
- labels
- todos
- metrics
- group
- parent
- children
- updated_by
- last_edited_by
- closed_by
- start_date_sourcing_milestone
- due_date_sourcing_milestone
- start_date_sourcing_epic
- due_date_sourcing_epic
- events
- resource_label_events
- user_mentions
\ No newline at end of file
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