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

Add latest changes from gitlab-org/gitlab@master

parent b3a736ed
No related branches found
No related tags found
No related merge requests found
Showing
with 270 additions and 28 deletions
---
title: Refactored snippets view to Vue
merge_request: 25188
author:
type: other
---
title: Update loading icon in Value Stream Analytics view
merge_request: 24861
author:
type: other
---
title: Fix code line and line number alignment in Safari
merge_request: 24820
author:
type: fixed
# Epics API **(ULTIMATE)**
# Epics API **(PREMIUM)**
 
Every API call to epic must be authenticated.
 
Loading
Loading
doc/ci/review_apps/img/enable_review_app_v12_8.png

45.3 KiB

Loading
Loading
@@ -55,9 +55,31 @@ The process of configuring Review Apps is as follows:
 
1. Set up the infrastructure to host and deploy the Review Apps (check the [examples](#review-apps-examples) below).
1. [Install](https://docs.gitlab.com/runner/install/) and [configure](https://docs.gitlab.com/runner/commands/) a Runner to do deployment.
1. Set up a job in `.gitlab-ci.yml` that uses the [predefined CI environment variable](../variables/README.md) `${CI_COMMIT_REF_NAME}` to create dynamic environments and restrict it to run only on branches.
1. Set up a job in `.gitlab-ci.yml` that uses the [predefined CI environment variable](../variables/README.md) `${CI_COMMIT_REF_NAME}`
to create dynamic environments and restrict it to run only on branches.
Alternatively, you can get a YML template for this job by [enabling review apps](#enable-review-apps-button) for your project.
1. Optionally, set a job that [manually stops](../environments.md#stopping-an-environment) the Review Apps.
 
### Enable Review Apps button
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/118844) in GitLab 12.8.
When configuring Review Apps for a project, you need to add a new job to `.gitlab-ci.yml`,
as mentioned above. To facilitate this and if you are using Kubernetes, you can click
the **Enable Review Apps** button and GitLab will prompt you with a template code block that
you can copy and paste into `.gitlab-ci.yml` as a starting point. To do so:
1. Go to the project your want to create a Review App job for.
1. From the left nav, go to **Operations** > **Environments**.
1. Click on the **Enable Review Apps** button. It is available to you
if you have Developer or higher [permissions](../../user/permissions.md) to that project.
1. Copy the provided code snippet and paste it into your
`.gitlab-ci.yml` file:
![Enable Review Apps modal](img/enable_review_app_v12_8.png)
1. Feel free to tune this template to your own needs.
## Review Apps examples
 
The following are example projects that demonstrate Review App configuration:
Loading
Loading
Loading
Loading
@@ -93,6 +93,7 @@ The following table depicts the various user permission levels in a project.
| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
| Create new environments | | | ✓ | ✓ | ✓ |
| Stop environments | | | ✓ | ✓ | ✓ |
| Enable Review Apps | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ (*5*) | ✓ | ✓ |
Loading
Loading
doc/user/project/insights/img/insights_example_pie_chart.png

6.82 KiB

Loading
Loading
@@ -96,7 +96,7 @@ The following table lists available parameters for charts:
| Keyword | Description |
|:---------------------------------------------------|:------------|
| [`title`](#title) | The title of the chart. This will displayed on the Insights page. |
| [`type`](#type) | The type of chart: `bar`, `line`, `stacked-bar`, `pie` etc. |
| [`type`](#type) | The type of chart: `bar`, `line` or `stacked-bar`. |
| [`query`](#query) | A hash that defines the conditions for issues / merge requests to be part of the chart. |
 
## Parameter details
Loading
Loading
@@ -132,7 +132,6 @@ Supported values are:
| ----- | ------- |
| `bar` | ![Insights example bar chart](img/insights_example_bar_chart.png) |
| `bar` (time series, i.e. when `group_by` is used) | ![Insights example bar time series chart](img/insights_example_bar_time_series_chart.png) |
| `pie` | ![Insights example pie chart](img/insights_example_pie_chart.png) |
| `line` | ![Insights example stacked bar chart](img/insights_example_line_chart.png) |
| `stacked-bar` | ![Insights example stacked bar chart](img/insights_example_stacked_bar_chart.png) |
 
Loading
Loading
Loading
Loading
@@ -2,26 +2,21 @@
 
module QA
context 'Verify' do
describe 'CI variable support' do
it 'user adds a CI variable', :smoke do
Flow::Login.sign_in
project = Resource::Project.fabricate_via_api! do |project|
describe 'Add or Remove CI variable via UI', :smoke do
let!(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'project-with-ci-variables'
project.description = 'project with CI variables'
end
end
 
Resource::CiVariable.fabricate_via_api! do |resource|
resource.project = project
resource.key = 'VARIABLE_KEY'
resource.value = 'some_CI_variable'
resource.masked = false
end
project.visit!
Page::Project::Menu.perform(&:go_to_ci_cd_settings)
before do
Flow::Login.sign_in
add_ci_variable
open_ci_cd_settings
end
 
it 'user adds a CI variable' do
Page::Project::Settings::CICD.perform do |settings|
settings.expand_ci_variables do |page|
expect(page).to have_field(with: 'VARIABLE_KEY')
Loading
Loading
@@ -33,6 +28,32 @@ module QA
end
end
end
it 'user removes a CI variable' do
Page::Project::Settings::CICD.perform do |settings|
settings.expand_ci_variables do |page|
page.remove_variable
expect(page).not_to have_field(with: 'VARIABLE_KEY')
end
end
end
private
def add_ci_variable
Resource::CiVariable.fabricate_via_browser_ui! do |ci_variable|
ci_variable.project = project
ci_variable.key = 'VARIABLE_KEY'
ci_variable.value = 'some_CI_variable'
ci_variable.masked = false
end
end
def open_ci_cd_settings
project.visit!
Page::Project::Menu.perform(&:go_to_ci_cd_settings)
end
end
end
end
Loading
Loading
@@ -10,13 +10,13 @@ module QA
RetriesExceededError = Class.new(RuntimeError)
WaitExceededError = Class.new(RuntimeError)
 
def repeat_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false)
def repeat_until(max_attempts: nil, max_duration: nil, reload_page: nil, sleep_interval: 0, raise_on_failure: true, retry_on_exception: false, log: true)
attempts = 0
start = Time.now
 
begin
while remaining_attempts?(attempts, max_attempts) && remaining_time?(start, max_duration)
QA::Runtime::Logger.debug("Attempt number #{attempts + 1}") if max_attempts
QA::Runtime::Logger.debug("Attempt number #{attempts + 1}") if max_attempts && log
 
result = yield
return result if result
Loading
Loading
Loading
Loading
@@ -6,7 +6,7 @@ module QA
module_function
 
def wait_for_requests
Waiter.wait_until do
Waiter.wait_until(log: false) do
finished_all_ajax_requests? && finished_all_axios_requests?
end
end
Loading
Loading
Loading
Loading
@@ -7,15 +7,17 @@ module QA
 
module_function
 
def wait_until(max_duration: singleton_class::DEFAULT_MAX_WAIT_TIME, reload_page: nil, sleep_interval: 0.1, raise_on_failure: true, retry_on_exception: false)
QA::Runtime::Logger.debug(
<<~MSG.tr("\n", ' ')
with wait_until: max_duration: #{max_duration};
reload_page: #{reload_page};
sleep_interval: #{sleep_interval};
raise_on_failure: #{raise_on_failure}
MSG
)
def wait_until(max_duration: singleton_class::DEFAULT_MAX_WAIT_TIME, reload_page: nil, sleep_interval: 0.1, raise_on_failure: true, retry_on_exception: false, log: true)
if log
QA::Runtime::Logger.debug(
<<~MSG.tr("\n", ' ')
with wait_until: max_duration: #{max_duration};
reload_page: #{reload_page};
sleep_interval: #{sleep_interval};
raise_on_failure: #{raise_on_failure}
MSG
)
end
 
result = nil
self.repeat_until(
Loading
Loading
@@ -23,11 +25,12 @@ module QA
reload_page: reload_page,
sleep_interval: sleep_interval,
raise_on_failure: raise_on_failure,
retry_on_exception: retry_on_exception
retry_on_exception: retry_on_exception,
log: log
) do
result = yield
end
QA::Runtime::Logger.debug("ended wait_until")
QA::Runtime::Logger.debug("ended wait_until") if log
 
result
end
Loading
Loading
Loading
Loading
@@ -381,5 +381,35 @@ describe QA::Support::Repeater do
end
end
end
it 'logs attempts' do
attempted = false
expect do
subject.repeat_until(max_attempts: 1) do
unless attempted
attempted = true
break false
end
true
end
end.to output(/Attempt number/).to_stdout_from_any_process
end
it 'allows logging to be silenced' do
attempted = false
expect do
subject.repeat_until(max_attempts: 1, log: false) do
unless attempted
attempted = true
break false
end
true
end
end.not_to output.to_stdout_from_any_process
end
end
end
Loading
Loading
@@ -34,6 +34,11 @@ describe QA::Support::Waiter do
end
end
 
it 'allows logs to be silenced' do
expect { subject.wait_until(max_duration: 0, raise_on_failure: false, log: false) { false } }
.not_to output.to_stdout_from_any_process
end
it 'sets max_duration to 60 by default' do
expect(subject).to receive(:repeat_until).with(hash_including(max_duration: 60))
 
Loading
Loading
Loading
Loading
@@ -45,6 +45,7 @@ describe MetricsDashboard do
it 'returns the specified dashboard' do
expect(json_response['dashboard']['dashboard']).to eq('Environment metrics')
expect(json_response).not_to have_key('all_dashboards')
expect(json_response).not_to have_key('metrics_data')
end
 
context 'when the params are in an alternate format' do
Loading
Loading
@@ -53,6 +54,25 @@ describe MetricsDashboard do
it 'returns the specified dashboard' do
expect(json_response['dashboard']['dashboard']).to eq('Environment metrics')
expect(json_response).not_to have_key('all_dashboards')
expect(json_response).not_to have_key('metrics_data')
end
end
context 'when environment for dashboard is available' do
let(:params) { { environment: environment } }
before do
allow(controller).to receive(:project).and_return(project)
allow(controller).to receive(:environment).and_return(environment)
allow(controller)
.to receive(:metrics_dashboard_params)
.and_return(params)
end
it 'returns the specified dashboard' do
expect(json_response['dashboard']['dashboard']).to eq('Environment metrics')
expect(json_response).not_to have_key('all_dashboards')
expect(json_response).to have_key('metrics_data')
end
end
 
Loading
Loading
Loading
Loading
@@ -489,7 +489,7 @@ describe Projects::EnvironmentsController do
end
 
shared_examples_for '200 response' do
let(:expected_keys) { %w(dashboard status) }
let(:expected_keys) { %w(dashboard status metrics_data) }
 
it_behaves_like 'correctly formatted response', :ok
end
Loading
Loading
import { shallowMount } from '@vue/test-utils';
import BlobContentError from '~/blob/components/blob_content_error.vue';
describe('Blob Content Error component', () => {
let wrapper;
const viewerError = '<h1 id="error">Foo Error</h1>';
function createComponent() {
wrapper = shallowMount(BlobContentError, {
propsData: {
viewerError,
},
});
}
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders the passed error without transformations', () => {
expect(wrapper.html()).toContain(viewerError);
});
});
import { shallowMount } from '@vue/test-utils';
import BlobContent from '~/blob/components/blob_content.vue';
import BlobContentError from '~/blob/components/blob_content_error.vue';
import {
RichViewerMock,
SimpleViewerMock,
RichBlobContentMock,
SimpleBlobContentMock,
} from './mock_data';
import { GlLoadingIcon } from '@gitlab/ui';
import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
describe('Blob Content component', () => {
let wrapper;
function createComponent(propsData = {}, activeViewer = SimpleViewerMock) {
wrapper = shallowMount(BlobContent, {
propsData: {
loading: false,
activeViewer,
...propsData,
},
});
}
afterEach(() => {
wrapper.destroy();
});
describe('rendering', () => {
it('renders loader if `loading: true`', () => {
createComponent({ loading: true });
expect(wrapper.contains(GlLoadingIcon)).toBe(true);
expect(wrapper.contains(BlobContentError)).toBe(false);
expect(wrapper.contains(RichViewer)).toBe(false);
expect(wrapper.contains(SimpleViewer)).toBe(false);
});
it('renders error if there is any in the viewer', () => {
const renderError = 'Oops';
const viewer = Object.assign({}, SimpleViewerMock, { renderError });
createComponent({}, viewer);
expect(wrapper.contains(GlLoadingIcon)).toBe(false);
expect(wrapper.contains(BlobContentError)).toBe(true);
expect(wrapper.contains(RichViewer)).toBe(false);
expect(wrapper.contains(SimpleViewer)).toBe(false);
});
it.each`
type | mock | viewer
${'simple'} | ${SimpleViewerMock} | ${SimpleViewer}
${'rich'} | ${RichViewerMock} | ${RichViewer}
`(
'renders $type viewer when activeViewer is $type and no loading or error detected',
({ mock, viewer }) => {
createComponent({}, mock);
expect(wrapper.contains(viewer)).toBe(true);
},
);
it.each`
content | mock | viewer
${SimpleBlobContentMock.plainData} | ${SimpleViewerMock} | ${SimpleViewer}
${RichBlobContentMock.richData} | ${RichViewerMock} | ${RichViewer}
`('renders correct content that is passed to the component', ({ content, mock, viewer }) => {
createComponent({ content }, mock);
expect(wrapper.find(viewer).html()).toContain(content);
});
});
});
Loading
Loading
@@ -67,13 +67,4 @@ describe('Blob Header Default Actions', () => {
expect(buttons.at(0).attributes('disabled')).toBeTruthy();
});
});
describe('functionally', () => {
it('emits an event when a Copy Contents button is clicked', () => {
jest.spyOn(wrapper.vm, '$emit');
buttons.at(0).vm.$emit('click');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('copy');
});
});
});
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