diff --git a/.gitignore b/.gitignore
index b8cbfe9966d..231541a2237 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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/
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 205bf4a5a26..bcb86a33138 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -148,26 +148,6 @@ module Issuable
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?
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 3f3e6b2f31a..7e5a94fc0a1 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -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
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index e92042d1056..93234cbdfe1 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -103,7 +103,7 @@ class MergeRequest < ApplicationRecord
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
diff --git a/changelogs/unreleased/7597-add-template-repository-usage-to-the-usage-ping.yml b/changelogs/unreleased/7597-add-template-repository-usage-to-the-usage-ping.yml
new file mode 100644
index 00000000000..f9479c3eef4
--- /dev/null
+++ b/changelogs/unreleased/7597-add-template-repository-usage-to-the-usage-ping.yml
@@ -0,0 +1,5 @@
+---
+title: Add template repository usage to the usage ping
+merge_request: 20126
+author: minghuan lei
+type: changed
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index a5486e450d4..711fd4ef3c2 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -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
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index df4f49524bc..f3635613339 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -291,7 +291,7 @@ Settings.pages['url'] ||= Settings.__send__(:build_pages_url)
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
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index 33dd9dd9b6f..724bc240bc2 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -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).
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index 5f2cbc24c60..4723bfbf261 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -121,7 +121,7 @@ module Banzai
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
diff --git a/lib/banzai/filter/spaced_link_filter.rb b/lib/banzai/filter/spaced_link_filter.rb
index ee7f10ebdf6..101b55a49e4 100644
--- a/lib/banzai/filter/spaced_link_filter.rb
+++ b/lib/banzai/filter/spaced_link_filter.rb
@@ -77,7 +77,7 @@ module Banzai
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
diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb
index ffad00fa7d7..dd7ab92c6ae 100644
--- a/lib/gitlab/dependency_linker/base_linker.rb
+++ b/lib/gitlab/dependency_linker/base_linker.rb
@@ -62,7 +62,7 @@ module Gitlab
end
def link_tag(name, url)
- %{#{ERB::Util.html_escape_once(name)}}
+ %{#{ERB::Util.html_escape_once(name)}}.html_safe
end
# Links package names based on regex.
diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb
index 1bbde1ffd2a..29dff699ba5 100644
--- a/lib/gitlab/diff/inline_diff_marker.rb
+++ b/lib/gitlab/diff/inline_diff_marker.rb
@@ -9,7 +9,7 @@ module Gitlab
def mark(line_inline_diffs, mode: nil)
super(line_inline_diffs) do |text, left:, right:|
- %{#{text}}
+ %{#{text}}.html_safe
end
end
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0bd39d4cdcf..ecb1f1996d9 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -373,6 +373,14 @@ FactoryBot.define do
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' }
diff --git a/spec/javascripts/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
similarity index 51%
rename from spec/javascripts/pipelines/graph/job_item_spec.js
rename to spec/frontend/pipelines/graph/job_item_spec.js
index 1cdb0aff524..943ef9d3531 100644
--- a/spec/javascripts/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -1,10 +1,13 @@
-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})`,
+ );
});
});
});
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
new file mode 100644
index 00000000000..c355d653203
--- /dev/null
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -0,0 +1,127 @@
+import { mount } from '@vue/test-utils';
+import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
+
+import mockData from './linked_pipelines_mock_data';
+
+const mockPipeline = mockData.triggered[0];
+
+describe('Linked pipeline', () => {
+ let wrapper;
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('rendered output', () => {
+ const props = {
+ pipeline: mockPipeline,
+ };
+
+ beforeEach(() => {
+ wrapper = mount(LinkedPipelineComponent, {
+ sync: false,
+ attachToDocument: true,
+ propsData: props,
+ });
+ });
+
+ it('should render a list item as the containing element', () => {
+ expect(wrapper.is('li')).toBe(true);
+ });
+
+ it('should render a button', () => {
+ const linkElement = wrapper.find('.js-linked-pipeline-content');
+
+ expect(linkElement.exists()).toBe(true);
+ });
+
+ it('should render the project name', () => {
+ expect(wrapper.text()).toContain(props.pipeline.project.name);
+ });
+
+ it('should render an svg within the status container', () => {
+ const pipelineStatusElement = wrapper.find('.js-linked-pipeline-status');
+
+ expect(pipelineStatusElement.find('svg').exists()).toBe(true);
+ });
+
+ it('should render the pipeline status icon svg', () => {
+ expect(wrapper.find('.js-ci-status-icon-running').exists()).toBe(true);
+ expect(wrapper.find('.js-ci-status-icon-running').html()).toContain('