Commit 36b85ee2 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent e04b145fbfdf
......@@ -66,7 +66,7 @@ eslint-report.html
/vendor/gitaly-ruby
/builds*
/.gitlab_workhorse_secret
/.gitlab_pages_shared_secret
/.gitlab_pages_secret
/webpack-report/
/knapsack/
/rspec_flaky/
......
......@@ -148,26 +148,6 @@ def award_emojis_loaded?
strip_attributes :title
# The state_machine gem will reset the value of state_id unless it
# is a raw attribute passed in here:
# https://gitlab.com/gitlab-org/gitlab/issues/35746#note_241148787
#
# This assumes another initialize isn't defined. Otherwise this
# method may need to be prepended.
def initialize(attributes = nil)
if attributes.is_a?(Hash)
attr = attributes.symbolize_keys
if attr.key?(:state) && !attr.key?(:state_id)
value = attr.delete(:state)
state_id = self.class.available_states[value]
attributes[:state_id] = state_id if state_id
end
end
super(attributes)
end
# We want to use optimistic lock for cases when only title or description are involved
# http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
def locking_enabled?
......
......@@ -77,7 +77,7 @@ class Issue < ApplicationRecord
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
state_machine :state_id, initial: :opened do
state_machine :state_id, initial: :opened, initialize: false do
event :close do
transition [:opened] => :closed
end
......
......@@ -103,7 +103,7 @@ def self.available_state_names
super + [:merged, :locked]
end
state_machine :state_id, initial: :opened do
state_machine :state_id, initial: :opened, initialize: false do
event :close do
transition [:opened] => :closed
end
......
---
title: Add template repository usage to the usage ping
merge_request: 20126
author: minghuan lei
type: changed
......@@ -321,8 +321,8 @@ production: &base
# external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages
# File that contains the shared secret key for verifying access for gitlab-pages.
# Default is '.gitlab_pages_shared_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_pages_shared_secret
# Default is '.gitlab_pages_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_pages_secret
## Mattermost
## For enabling Add to Mattermost button
......
......@@ -291,7 +291,7 @@
Settings.pages['external_http'] ||= false unless Settings.pages['external_http'].present?
Settings.pages['external_https'] ||= false unless Settings.pages['external_https'].present?
Settings.pages['artifacts_server'] ||= Settings.pages['enabled'] if Settings.pages['artifacts_server'].nil?
Settings.pages['secret_file'] ||= Rails.root.join('.gitlab_pages_shared_secret')
Settings.pages['secret_file'] ||= Rails.root.join('.gitlab_pages_secret')
#
# Geo
......
......@@ -336,6 +336,49 @@ Generated docker images should have the program at their `Entrypoint` to create
portable commands. That way, anyone can run the image, and without parameters
it will display its help message (if `cli` has been used).
## Distributing Go binaries
With the exception of [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner),
which publishes its own binaries, our Go binaries are created by projects
managed by the [Distribution group](https://about.gitlab.com/handbook/product/categories/#distribution-group).
The [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab) project creates a
single, monolithic operating system package containing all the binaries, while
the [Cloud-Native GitLab (CNG)](https://gitlab.com/gitlab-org/build/CNG) project
publishes a set of Docker images and Helm charts to glue them together.
Both approaches use the same version of Go for all projects, so it's important
to ensure all our Go-using projects have at least one Go version in common in
their test matrices. You can check the version of Go currently being used by
[Omnibus](https://gitlab.com/gitlab-org/gitlab-omnibus-builder/blob/master/docker/Dockerfile_debian_10#L59),
and the version being used for [CNG](https://gitlab.com/gitlab-org/build/cng/blob/master/ci_files/variables.yml#L12).
### Updating Go version
We should always use a [supported version](https://golang.org/doc/devel/release.html#policy)
of Go, i.e., one of the three most recent minor releases, and should always use
the most recent patch-level for that version, as it may contain security fixes.
Changing the version affects every project being compiled, so it's important to
ensure that all projects have been updated to test against the new Go version
before changing the package builders to use it. Despite [Go's compatibility promise](https://golang.org/doc/go1compat),
changes between minor versions can expose bugs or cause problems in our projects.
Once you've picked a new Go version to use, the steps to update Omnibus and CNG
are:
- [Create a merge request in the CNG project](https://gitlab.com/gitlab-org/build/CNG/edit/master/ci_files/variables.yml?branch_name=update-go-version),
updating the `GO_VERSION` in `ci_files/variables.yml`.
- Create a merge request in the [`gitlab-omnibus-builder` project](https://gitlab.com/gitlab-org/gitlab-omnibus-builder),
updating every file in the `docker/` directory so the `GO_VERSION` is set
appropriately. [Here's an example](https://gitlab.com/gitlab-org/gitlab-omnibus-builder/merge_requests/125/diffs).
- Tag a new release of `gitlab-omnibus-builder` containing the change.
- [Create a merge request in the `gitlab-omnibus` project](https://gitlab.com/gitlab-org/omnibus-gitlab/edit/master/.gitlab-ci.yml?branch_name=update-gitlab-omnibus-builder-version),
updating the `BUILDER_IMAGE_REVISION` to match the newly-created tag.
To reduce unnecessary differences between two distribution methods, Omnibus and
CNG **should always use the same Go version**.
---
[Return to Development documentation](../README.md).
......@@ -121,7 +121,7 @@ def autolink_match(match)
def autolink_filter(text)
Gitlab::StringRegexMarker.new(CGI.unescapeHTML(text), text.html_safe).mark(LINK_PATTERN) do |link, left:, right:|
autolink_match(link)
autolink_match(link).html_safe
end
end
......
......@@ -77,7 +77,7 @@ def spaced_link_match(link)
def spaced_link_filter(text)
Gitlab::StringRegexMarker.new(CGI.unescapeHTML(text), text.html_safe).mark(LINK_OR_IMAGE_PATTERN) do |link, left:, right:|
spaced_link_match(link)
spaced_link_match(link).html_safe
end
end
......
......@@ -62,7 +62,7 @@ def github_url(name)
end
def link_tag(name, url)
%{<a href="#{ERB::Util.html_escape_once(url)}" rel="nofollow noreferrer noopener" target="_blank">#{ERB::Util.html_escape_once(name)}</a>}
%{<a href="#{ERB::Util.html_escape_once(url)}" rel="nofollow noreferrer noopener" target="_blank">#{ERB::Util.html_escape_once(name)}</a>}.html_safe
end
# Links package names based on regex.
......
......@@ -9,7 +9,7 @@ def initialize(line, rich_line = nil)
def mark(line_inline_diffs, mode: nil)
super(line_inline_diffs) do |text, left:, right:|
%{<span class="#{html_class_names(left, right, mode)}">#{text}</span>}
%{<span class="#{html_class_names(left, right, mode)}">#{text}</span>}.html_safe
end
end
......
......@@ -373,6 +373,14 @@
end
end
trait :license_management do
options do
{
artifacts: { reports: { license_management: 'gl-license-management-report.json' } }
}
end
end
trait :non_playable do
status { 'created' }
self.when { 'manual' }
......
import Vue from 'vue';
import { trimText } from 'helpers/text_helper';
import { mount } from '@vue/test-utils';
import JobItem from '~/pipelines/components/graph/job_item.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('pipeline graph job item', () => {
const JobComponent = Vue.extend(JobItem);
let component;
let wrapper;
const createWrapper = propsData => {
wrapper = mount(JobItem, { sync: false, attachToDocument: true, propsData });
};
const delayedJobFixture = getJSONFixture('jobs/delayed.json');
const mockJob = {
......@@ -28,27 +31,25 @@ describe('pipeline graph job item', () => {
};
afterEach(() => {
component.$destroy();
wrapper.destroy();
});
describe('name with link', () => {
it('should render the job name and status with a link', done => {
component = mountComponent(JobComponent, { job: mockJob });
createWrapper({ job: mockJob });
Vue.nextTick(() => {
const link = component.$el.querySelector('a');
wrapper.vm.$nextTick(() => {
const link = wrapper.find('a');
expect(link.getAttribute('href')).toEqual(mockJob.status.details_path);
expect(link.attributes('href')).toBe(mockJob.status.details_path);
expect(link.getAttribute('data-original-title')).toEqual(
expect(link.attributes('data-original-title')).toEqual(
`${mockJob.name} - ${mockJob.status.label}`,
);
expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined();
expect(wrapper.find('.js-status-icon-success')).toBeDefined();
expect(component.$el.querySelector('.ci-status-text').textContent.trim()).toEqual(
mockJob.name,
);
expect(trimText(wrapper.find('.ci-status-text').text())).toBe(mockJob.name);
done();
});
......@@ -57,7 +58,7 @@ describe('pipeline graph job item', () => {
describe('name without link', () => {
it('it should render status and name', () => {
component = mountComponent(JobComponent, {
createWrapper({
job: {
id: 4257,
name: 'test',
......@@ -72,36 +73,34 @@ describe('pipeline graph job item', () => {
},
});
expect(component.$el.querySelector('.js-status-icon-success')).toBeDefined();
expect(component.$el.querySelector('a')).toBeNull();
expect(wrapper.find('.js-status-icon-success')).toBeDefined();
expect(wrapper.find('a').exists()).toBe(false);
expect(component.$el.querySelector('.ci-status-text').textContent.trim()).toEqual(
mockJob.name,
);
expect(trimText(wrapper.find('.ci-status-text').text())).toEqual(mockJob.name);
});
});
describe('action icon', () => {
it('it should render the action icon', () => {
component = mountComponent(JobComponent, { job: mockJob });
createWrapper({ job: mockJob });
expect(component.$el.querySelector('a.ci-action-icon-container')).toBeDefined();
expect(component.$el.querySelector('i.ci-action-icon-wrapper')).toBeDefined();
expect(wrapper.find('a.ci-action-icon-container')).toBeDefined();
expect(wrapper.find('i.ci-action-icon-wrapper')).toBeDefined();
});
});
it('should render provided class name', () => {
component = mountComponent(JobComponent, {
createWrapper({
job: mockJob,
cssClassJobName: 'css-class-job-name',
});
expect(component.$el.querySelector('a').classList.contains('css-class-job-name')).toBe(true);
expect(wrapper.find('a').classes()).toContain('css-class-job-name');
});
describe('status label', () => {
it('should not render status label when it is not provided', () => {
component = mountComponent(JobComponent, {
createWrapper({
job: {
id: 4258,
name: 'test',
......@@ -111,15 +110,13 @@ describe('pipeline graph job item', () => {
},
});
expect(
component.$el
.querySelector('.js-job-component-tooltip')
.getAttribute('data-original-title'),
).toEqual('test');
expect(wrapper.find('.js-job-component-tooltip').attributes('data-original-title')).toBe(
'test',
);
});
it('should not render status label when it is provided', () => {
component = mountComponent(JobComponent, {
createWrapper({
job: {
id: 4259,
name: 'test',
......@@ -131,25 +128,21 @@ describe('pipeline graph job item', () => {
},
});
expect(
component.$el
.querySelector('.js-job-component-tooltip')
.getAttribute('data-original-title'),
).toEqual('test - success');
expect(wrapper.find('.js-job-component-tooltip').attributes('data-original-title')).toEqual(
'test - success',
);
});
});
describe('for delayed job', () => {
it('displays remaining time in tooltip', () => {
component = mountComponent(JobComponent, {
createWrapper({
job: delayedJobFixture,
});
expect(
component.$el
.querySelector('.js-pipeline-graph-job-link')
.getAttribute('data-original-title'),
).toEqual(`delayed job - delayed manual action (${component.remainingTime})`);
expect(wrapper.find('.js-pipeline-graph-job-link').attributes('data-original-title')).toEqual(
`delayed job - delayed manual action (${wrapper.vm.remainingTime})`,
);
});
});
});
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './linked_pipelines_mock_data';
const mockPipeline = mockData.triggered[0];
describe('Linked pipeline', () => {
const Component = Vue.extend(LinkedPipelineComponent);
let vm;
let wrapper;
afterEach(() => {
vm.$destroy();
wrapper.destroy();
});
describe('rendered output', () => {
......@@ -19,57 +18,61 @@ describe('Linked pipeline', () => {
};
beforeEach(() => {
vm = mountComponent(Component, props);
wrapper = mount(LinkedPipelineComponent, {
sync: false,
attachToDocument: true,
propsData: props,
});
});
it('should render a list item as the containing element', () => {
expect(vm.$el.tagName).toBe('LI');
expect(wrapper.is('li')).toBe(true);
});
it('should render a button', () => {
const linkElement = vm.$el.querySelector('.js-linked-pipeline-content');
const linkElement = wrapper.find('.js-linked-pipeline-content');
expect(linkElement).not.toBeNull();
expect(linkElement.exists()).toBe(true);
});
it('should render the project name', () => {
expect(vm.$el.innerText).toContain(props.pipeline.project.name);
expect(wrapper.text()).toContain(props.pipeline.project.name);
});
it('should render an svg within the status container', () => {
const pipelineStatusElement = vm.$el.querySelector('.js-linked-pipeline-status');
const pipelineStatusElement = wrapper.find('.js-linked-pipeline-status');
expect(pipelineStatusElement.querySelector('svg')).not.toBeNull();
expect(pipelineStatusElement.find('svg').exists()).toBe(true);
});
it('should render the pipeline status icon svg', () => {
expect(vm.$el.querySelector('.js-ci-status-icon-running')).not.toBeNull();
expect(vm.$el.querySelector('.js-ci-status-icon-running').innerHTML).toContain('<svg');
expect(wrapper.find('.js-ci-status-icon-running').exists()).toBe(true);
expect(wrapper.find('.js-ci-status-icon-running').html()).toContain('<svg');
});
it('should have a ci-status child component', () => {
expect(vm.$el.querySelector('.js-linked-pipeline-status')).not.toBeNull();
expect(wrapper.find('.js-linked-pipeline-status').exists()).toBe(true);
});
it('should render the pipeline id', () => {
expect(vm.$el.innerText).toContain(`#${props.pipeline.id}`);
expect(wrapper.text()).toContain(`#${props.pipeline.id}`);
});
it('should correctly compute the tooltip text', () => {
expect(vm.tooltipText).toContain(mockPipeline.project.name);
expect(vm.tooltipText).toContain(mockPipeline.details.status.label);
expect(wrapper.vm.tooltipText).toContain(mockPipeline.project.name);
expect(wrapper.vm.tooltipText).toContain(mockPipeline.details.status.label);
});
it('should render the tooltip text as the title attribute', () => {
const tooltipRef = vm.$el.querySelector('.js-linked-pipeline-content');
const titleAttr = tooltipRef.getAttribute('data-original-title');
const tooltipRef = wrapper.find('.js-linked-pipeline-content');
const titleAttr = tooltipRef.attributes('data-original-title');
expect(titleAttr).toContain(mockPipeline.project.name);
expect(titleAttr).toContain(mockPipeline.details.status.label);
});
it('does not render the loading icon when isLoading is false', () => {
expect(vm.$el.querySelector('.js-linked-pipeline-loading')).toBeNull();
expect(wrapper.find('.js-linked-pipeline-loading').exists()).toBe(false);
});
});
......@@ -79,11 +82,15 @@ describe('Linked pipeline', () => {
};
beforeEach(() => {
vm = mountComponent(Component, props);
wrapper = mount(LinkedPipelineComponent, {
sync: false,
attachToDocument: true,
propsData: props,
});
});
it('renders a loading icon', () => {
expect(vm.$el.querySelector('.js-linked-pipeline-loading')).not.toBeNull();
expect(wrapper.find('.js-linked-pipeline-loading').exists()).toBe(true);
});
});
......@@ -93,21 +100,25 @@ describe('Linked pipeline', () => {
};
beforeEach(() => {
vm = mountComponent(Component, props);
wrapper = mount(LinkedPipelineComponent, {
sync: false,
attachToDocument: true,
propsData: props,
});
});
it('emits `pipelineClicked` event', () => {
spyOn(vm, '$emit');
vm.$el.querySelector('button').click();
jest.spyOn(wrapper.vm, '$emit');
wrapper.find('button').trigger('click');
expect(vm.$emit).toHaveBeenCalledWith('pipelineClicked');
expect(wrapper.vm.$emit).toHaveBeenCalledWith('pipelineClicked');
});
it('should emit `bv::hide::tooltip` to close the tooltip', () => {
spyOn(vm.$root, '$emit');
vm.$el.querySelector('button').click();
jest.spyOn(wrapper.vm.$root, '$emit');
wrapper.find('button').trigger('click');
expect(vm.$root.$emit.calls.argsFor(0)).toEqual([
expect(wrapper.vm.$root.$emit.mock.calls[0]).toEqual([
'bv::hide::tooltip',
'js-linked-pipeline-132',
]);
......
export default {
triggered_by: {
id: 129,
active: true,
path: '/gitlab-org/gitlab-foss/pipelines/129',
project: {
name: 'GitLabCE',
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-foss/pipelines/129',
favicon:
'/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',
},
},
flags: {
latest: false,
triggered: false,
stuck: false,
yaml_errors: false,
retryable: true,
cancelable: true,
},
ref: {
name: '7-5-stable',
path: '/gitlab-org/gitlab-foss/commits/7-5-stable',
tag: false,
branch: true,
},
commit: {
id: '23433d4d8b20d7e45c103d0b6048faad38a130ab',
short_id: '23433d4d',
title: 'Version 7.5.0.rc1',
created_at: '2014-11-17T15:44:14.000+01:00',
parent_ids: ['30ac909f30f58d319b42ed1537664483894b18cd'],
message: 'Version 7.5.0.rc1\n',
author_name: 'Jacob Vosmaer',
author_email: 'contact@jacobvosmaer.nl',
authored_date: '2014-11-17T15:44:14.000+01:00',
committer_name: 'Jacob Vosmaer',
committer_email: 'contact@jacobvosmaer.nl',
committed_date: '2014-11-17T15:44:14.000+01:00',
author_gravatar_url:
'http://www.gravatar.com/avatar/e66d11c0eedf8c07b3b18fca46599807?s=80&d=identicon',
commit_url:
'http://localhost:3000/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
commit_path: '/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab',
},
retry_path: '/gitlab-org/gitlab-foss/pipelines/129/retry',
cancel_path: '/gitlab-org/gitlab-foss/pipelines/129/cancel',
created_at: '2017-05-24T14:46:20.090Z',
updated_at: '2017-05-24T14:46:29.906Z',
},
triggered: [
{
id: 132,
active: true,
path: '/gitlab-org/gitlab-foss/pipelines/132',
project: {
name: 'GitLabCE',
},
details: {
status: {
icon: 'status_running',
text: 'running',
label: 'running',
group: 'running',
has_details: true,
details_path: '/gitlab-org/gitlab-foss/pipelines/132',
favicon:
'/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico',