Commit 404bb44e authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent d1421948
......@@ -11,7 +11,7 @@ schedule:package-and-qa:notify-success:
- .only-canonical-schedules
- .notify
script:
- 'scripts/notify-slack qa-master ":tada: Scheduled QA against `master` passed! :tada: See $CI_PIPELINE_URL." ci_passing'
- 'scripts/notify-slack qa-master ":tada: Scheduled QA against master passed! :tada: See $CI_PIPELINE_URL." ci_passing'
needs: ["schedule:package-and-qa"]
when: on_success
......@@ -20,6 +20,6 @@ schedule:package-and-qa:notify-failure:
- .only-canonical-schedules
- .notify
script:
- 'scripts/notify-slack qa-master ":skull_and_crossbones: Scheduled QA against `master` failed! :skull_and_crossbones: See $CI_PIPELINE_URL." ci_failing'
- 'scripts/notify-slack qa-master ":skull_and_crossbones: Scheduled QA against master failed! :skull_and_crossbones: See $CI_PIPELINE_URL." ci_failing'
needs: ["schedule:package-and-qa"]
when: on_failure
......@@ -21,14 +21,6 @@ export default {
type: String,
required: true,
},
/* This prop can be used to replace some of the `render_commit_status`
used across GitLab, this way we could use this vue component and add a
realtime status where it makes sense
realtime: {
type: Boolean,
required: false,
default: true,
}, */
},
data() {
return {
......@@ -47,6 +39,9 @@ export default {
this.service = new CommitPipelineService(this.endpoint);
this.initPolling();
},
beforeDestroy() {
this.poll.stop();
},
methods: {
successCallback(res) {
const { pipelines } = res.data;
......@@ -95,9 +90,6 @@ export default {
.catch(this.errorCallback);
},
},
destroy() {
this.poll.stop();
},
};
</script>
<template>
......
......@@ -6,7 +6,7 @@
The access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
```
```plaintext
10 => Guest access
20 => Reporter access
30 => Developer access
......@@ -18,14 +18,16 @@
Gets a list of access requests viewable by the authenticated user.
```
```plaintext
GET /groups/:id/access_requests
GET /projects/:id/access_requests
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| Attribute | Type | Required | Description |
| --------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
Example request:
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/access_requests
......@@ -59,14 +61,16 @@ Example response:
Requests access for the authenticated user to a group or project.
```
```plaintext
POST /groups/:id/access_requests
POST /projects/:id/access_requests
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| Attribute | Type | Required | Description |
| --------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
Example request:
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/access_requests
......@@ -90,16 +94,18 @@ Example response:
Approves an access request for the given user.
```
```plaintext
PUT /groups/:id/access_requests/:user_id/approve
PUT /projects/:id/access_requests/:user_id/approve
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the access requester |
| `access_level` | integer | no | A valid access level (defaults: `30`, developer access level) |
| Attribute | Type | Required | Description |
| -------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the access requester |
| `access_level` | integer | no | A valid access level (defaults: `30`, developer access level) |
Example request:
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/access_requests/:user_id/approve?access_level=20
......@@ -123,15 +129,17 @@ Example response:
Denies an access request for the given user.
```
```plaintext
DELETE /groups/:id/access_requests/:user_id
DELETE /projects/:id/access_requests/:user_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the access requester |
| Attribute | Type | Required | Description |
| --------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the access requester |
Example request:
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/:id/access_requests/:user_id
......
......@@ -68,6 +68,7 @@ description: 'Learn how to contribute to GitLab.'
- [Git LFS](lfs.md)
- [Developing against interacting components or features](interacting_components.md)
- [File uploads](uploads.md)
- [Auto DevOps development guide](auto_devops.md)
## Performance guides
......
# Auto DevOps development guide
This document provides a development guide for contributors to
[Auto DevOps](../topics/autodevops/index.md)
## Development
Auto DevOps builds on top of GitLab CI to create an automatic pipeline
based on your project contents. When Auto DevOps is enabled for a
project, the user does not need to explicitly include any pipeline configuration
through a [`.gitlab-ci.yml` file](../ci/yaml/README.md).
In the absence of a `.gitlab-ci.yml` file, the [Auto DevOps CI
template](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml)
is used implicitly to configure the pipeline for the project. This
template is a top-level template that includes other sub-templates,
which then defines jobs.
Some jobs use images that are built from external projects:
- [Auto Build](../topics/autodevops/index.md#auto-build) uses
[configuration](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml)
in which the `build` job uses an image that is built using the
[`auto-build-image`](https://gitlab.com/gitlab-org/cluster-integration/auto-build-image)
project.
- [Auto Deploy](../topics/autodevops/index.md#auto-deploy) uses
[configuration](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml)
in which the jobs defined in this template use an image that is built using the
[`auto-deploy-image`](https://gitlab.com/gitlab-org/cluster-integration/auto-deploy-image)
project. By default, the Helm chart defined in
[`auto-deploy-app`](https://gitlab.com/gitlab-org/charts/auto-deploy-app)
is used to deploy.
There are extra variables that get passed to the CI jobs when Auto
DevOps is enabled that are not present in a normal CI job. These can be
found in
[`ProjectAutoDevops`](https://gitlab.com/gitlab-org/gitlab/blob/bf69484afa94e091c3e1383945f60dbe4e8681af/app/models/project_auto_devops.rb).
## Development environment
Configuring [GDK for Auto
DevOps](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/auto_devops.md).
......@@ -1183,22 +1183,6 @@ As of GitLab 10.0, the supported buildpacks are:
The following restrictions apply.
### Private project support
CAUTION: **Caution:** Private project support in Auto DevOps is experimental.
When a project has been marked as private, GitLab's [Container
Registry][container-registry] requires authentication when downloading
containers. Auto DevOps will automatically provide the required authentication
information to Kubernetes, allowing temporary access to the registry.
Authentication credentials will be valid while the pipeline is running, allowing
for a successful initial deployment.
After the pipeline completes, Kubernetes will no longer be able to access the
Container Registry. **Restarting a pod, scaling a service, or other actions which
require on-going access to the registry may fail**. On-going secure access is
planned for a subsequent release.
### Private registry support
There is no documented way of using private container registry with Auto DevOps.
......@@ -1274,4 +1258,4 @@ curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https:/
## Development guides
Configuring [GDK for Auto DevOps](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/auto_devops.md).
[Development guide for Auto DevOps](../../development/auto_devops.md)
......@@ -129,17 +129,7 @@ module QA
end
def try_to_merge!
# The merge button is disabled on load
wait do
has_element?(:merge_button)
end
# The merge button is enabled via JS
wait(reload: false) do
!find_element(:merge_button).disabled?
end
merge_immediately
merge_immediately if ready_to_merge?
end
def merge!
......@@ -187,6 +177,18 @@ module QA
click_element :edit_button
end
def ready_to_merge?
# The merge button is disabled on load
wait do
has_element?(:merge_button)
end
# The merge button is enabled via JS
wait(reload: false) do
!find_element(:merge_button).disabled?
end
end
def view_email_patches
click_element :dropdown_toggle
visit_link_in_element(:download_email_patches)
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Commit pipeline status component when polling is not successful renders not found CI icon without loader 1`] = `
<div
class="ci-status-link"
>
<a>
<ciicon-stub
aria-label="Pipeline: not found"
cssclasses=""
data-container="body"
data-original-title="Pipeline: not found"
size="24"
status="[object Object]"
title=""
/>
</a>
</div>
`;
exports[`Commit pipeline status component when polling is successful renders CI icon without loader 1`] = `
<div
class="ci-status-link"
>
<a
href="/frontend-fixtures/pipelines-project/pipelines/47"
>
<ciicon-stub
aria-label="Pipeline: pending"
cssclasses=""
data-container="body"
data-original-title="Pipeline: pending"
size="24"
status="[object Object]"
title=""
/>
</a>
</div>
`;
import Visibility from 'visibilityjs';
import { GlLoadingIcon } from '@gitlab/ui';
import Poll from '~/lib/utils/poll';
import flash from '~/flash';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import { shallowMount } from '@vue/test-utils';
import { getJSONFixture } from '../helpers/fixtures';
jest.mock('~/lib/utils/poll');
jest.mock('visibilityjs');
jest.mock('~/flash');
const mockFetchData = jest.fn();
jest.mock('~/projects/tree/services/commit_pipeline_service', () =>
jest.fn().mockImplementation(() => ({
fetchData: mockFetchData.mockReturnValue(Promise.resolve()),
})),
);
describe('Commit pipeline status component', () => {
let wrapper;
const { pipelines } = getJSONFixture('pipelines/pipelines.json');
const { status: mockCiStatus } = pipelines[0].details;
const defaultProps = {
endpoint: 'endpoint',
};
const createComponent = (props = {}) => {
wrapper = shallowMount(CommitPipelineStatus, {
propsData: {
...defaultProps,
...props,
},
sync: false,
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
jest.clearAllMocks();
});
describe('Visibility management', () => {
describe('when component is hidden', () => {
beforeEach(() => {
Visibility.hidden.mockReturnValue(true);
createComponent();
});
it('does not start polling', () => {
const [pollInstance] = Poll.mock.instances;
expect(pollInstance.makeRequest).not.toHaveBeenCalled();
});
it('requests pipeline data', () => {
expect(mockFetchData).toHaveBeenCalled();
});
});
describe('when component is visible', () => {
beforeEach(() => {
Visibility.hidden.mockReturnValue(false);
createComponent();
});
it('starts polling', () => {
const [pollInstance] = [...Poll.mock.instances].reverse();
expect(pollInstance.makeRequest).toHaveBeenCalled();
});
});
describe('when component changes its visibility', () => {
it.each`
visibility | action
${false} | ${'restart'}
${true} | ${'stop'}
`(
'$action polling when component visibility becomes $visibility',
({ visibility, action }) => {
Visibility.hidden.mockReturnValue(!visibility);
createComponent();
const [pollInstance] = Poll.mock.instances;
expect(pollInstance[action]).not.toHaveBeenCalled();
Visibility.hidden.mockReturnValue(visibility);
const [visibilityHandler] = Visibility.change.mock.calls[0];
visibilityHandler();
expect(pollInstance[action]).toHaveBeenCalled();
},
);
});
});
it('stops polling when component is destroyed', () => {
createComponent();
wrapper.destroy();
const [pollInstance] = Poll.mock.instances;
expect(pollInstance.stop).toHaveBeenCalled();
});
describe('when polling', () => {
let pollConfig;
beforeEach(() => {
Poll.mockImplementation(config => {
pollConfig = config;
return { makeRequest: jest.fn(), restart: jest.fn(), stop: jest.fn() };
});
createComponent();
});
it('shows the loading icon at start', () => {
createComponent();
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
pollConfig.successCallback({
data: { pipelines: [] },
});
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
});
});
describe('is successful', () => {
beforeEach(() => {
pollConfig.successCallback({
data: { pipelines: [{ details: { status: mockCiStatus } }] },
});
return wrapper.vm.$nextTick();
});
it('renders CI icon without loader', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
describe('is not successful', () => {
beforeEach(() => {
pollConfig.errorCallback();
});
it('renders not found CI icon without loader', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('displays flash error message', () => {
expect(flash).toHaveBeenCalled();
});
});
});
});
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Commit pipeline status component', () => {
let vm;
let Component;
let mock;
const mockCiStatus = {
details_path: '/root/hello-world/pipelines/1',
favicon: 'canceled.ico',
group: 'canceled',
has_details: true,
icon: 'status_canceled',
label: 'canceled',
text: 'canceled',
};
beforeEach(() => {
Component = Vue.extend(commitPipelineStatus);
});
describe('While polling pipeline data successfully', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet('/dummy/endpoint').reply(() => {
const res = Promise.resolve([
200,
{
pipelines: [
{
details: {
status: mockCiStatus,
},
},
],
},
]);
return res;
});
vm = mountComponent(Component, {
endpoint: '/dummy/endpoint',
});
});
afterEach(() => {
vm.poll.stop();
vm.$destroy();
mock.restore();
});
it('shows the loading icon when polling is starting', done => {
expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
setTimeout(() => {
expect(vm.$el.querySelector('.loading-container')).toBe(null);
done();
});
});
it('contains a ciStatus when the polling is successful ', done => {
setTimeout(() => {
expect(vm.ciStatus).toEqual(mockCiStatus);
done();
});
});
it('contains a ci-status icon when polling is successful', done => {
setTimeout(() => {
expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null);
expect(vm.$el.querySelector('.ci-status-icon').classList).toContain(
`ci-status-icon-${mockCiStatus.group}`,
);
done();
});
});
});
describe('When polling data was not successful', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet('/dummy/endpoint').reply(502, {});
vm = new Component({
props: {
endpoint: '/dummy/endpoint',
},
});
});
afterEach(() => {
vm.poll.stop();
vm.$destroy();
mock.restore();
});
it('calls an errorCallback', done => {
spyOn(vm, 'errorCallback').and.callThrough();
vm.$mount();
setTimeout(() => {
expect(vm.errorCallback.calls.count()).toEqual(1);
done();
});
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment