Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-24 18:09:51 +00:00
parent a17eb314cf
commit 12ce3cc57c
55 changed files with 662 additions and 214 deletions

View File

@ -188,7 +188,7 @@ export default {
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader,
'gl-py-3 gl-h-full!': !list.isExpanded && !isSwimlanesHeader,
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
}"

View File

@ -42,7 +42,7 @@ export default {
</script>
<template>
<div class="gl-display-flex gl-align-items-center">
<div data-qa-selector="package-path" class="gl-display-flex gl-align-items-center">
<gl-icon data-testid="base-icon" name="project" class="gl-mx-3 gl-min-w-0" />
<gl-link data-testid="root-link" class="gl-text-gray-500 gl-min-w-0" :href="`/${rootLink}`">

View File

@ -1,5 +1,6 @@
/* eslint-disable no-console */
import { getCLS, getFID, getLCP } from 'web-vitals';
import { PERFORMANCE_TYPE_MARK, PERFORMANCE_TYPE_MEASURE } from '~/performance_constants';
const initVitalsLog = () => {
const reportVital = data => {
@ -16,6 +17,29 @@ const initVitalsLog = () => {
getLCP(reportVital);
};
const logUserTimingMetrics = () => {
const metricsProcessor = list => {
const entries = list.getEntries();
entries.forEach(entry => {
const { name, entryType, startTime, duration } = entry;
const typeMapper = {
PERFORMANCE_MARK: String.fromCodePoint(0x1f3af),
PERFORMANCE_MEASURE: String.fromCodePoint(0x1f4d0),
};
console.group(`${typeMapper[entryType]} ${name}`);
if (entryType === PERFORMANCE_TYPE_MARK) {
console.log(`Start time: ${startTime}`);
} else if (entryType === PERFORMANCE_TYPE_MEASURE) {
console.log(`Duration: ${duration}`);
}
console.log(entry);
console.groupEnd();
});
};
const observer = new PerformanceObserver(metricsProcessor);
observer.observe({ entryTypes: [PERFORMANCE_TYPE_MEASURE, PERFORMANCE_TYPE_MARK] });
};
const initPerformanceBarLog = () => {
console.log(
`%c ${String.fromCodePoint(0x1f98a)} GitLab performance bar`,
@ -23,6 +47,7 @@ const initPerformanceBarLog = () => {
);
initVitalsLog();
logUserTimingMetrics();
};
export default initPerformanceBarLog;

View File

@ -1,3 +1,6 @@
export const PERFORMANCE_TYPE_MARK = 'mark';
export const PERFORMANCE_TYPE_MEASURE = 'measure';
//
// SNIPPET namespace
//

View File

@ -1,7 +1,7 @@
<script>
import { escape, capitalize } from 'lodash';
import { GlLoadingIcon } from '@gitlab/ui';
import StageColumnComponent from './stage_column_component.vue';
import GraphMixin from '../../mixins/graph_component_mixin';
import GraphWidthMixin from '../../mixins/graph_width_mixin';
import LinkedPipelinesColumn from './linked_pipelines_column.vue';
import GraphBundleMixin from '../../mixins/graph_pipeline_bundle_mixin';
@ -13,7 +13,7 @@ export default {
GlLoadingIcon,
LinkedPipelinesColumn,
},
mixins: [GraphMixin, GraphWidthMixin, GraphBundleMixin],
mixins: [GraphWidthMixin, GraphBundleMixin],
props: {
isLoading: {
type: Boolean,
@ -51,6 +51,9 @@ export default {
};
},
computed: {
graph() {
return this.pipeline.details?.stages;
},
hasTriggeredBy() {
return (
this.type !== this.$options.downstream &&
@ -92,6 +95,39 @@ export default {
},
},
methods: {
capitalizeStageName(name) {
const escapedName = escape(name);
return capitalize(escapedName);
},
isFirstColumn(index) {
return index === 0;
},
stageConnectorClass(index, stage) {
let className;
// If it's the first stage column and only has one job
if (this.isFirstColumn(index) && stage.groups.length === 1) {
className = 'no-margin';
} else if (index > 0) {
// If it is not the first column
className = 'left-margin';
}
return className;
},
refreshPipelineGraph() {
this.$emit('refreshPipelineGraph');
},
/**
* CSS class is applied:
* - if pipeline graph contains only one stage column component
*
* @param {number} index
* @returns {boolean}
*/
shouldAddRightMargin(index) {
return !(index === this.graph.length - 1);
},
handleClickedDownstream(pipeline, clickedIndex, downstreamNode) {
/**
* Calculates the margin top of the clicked downstream pipeline by

View File

@ -1,54 +0,0 @@
import { escape } from 'lodash';
export default {
props: {
isLoading: {
type: Boolean,
required: true,
},
pipeline: {
type: Object,
required: true,
},
},
computed: {
graph() {
return this.pipeline.details && this.pipeline.details.stages;
},
},
methods: {
capitalizeStageName(name) {
const escapedName = escape(name);
return escapedName.charAt(0).toUpperCase() + escapedName.slice(1);
},
isFirstColumn(index) {
return index === 0;
},
stageConnectorClass(index, stage) {
let className;
// If it's the first stage column and only has one job
if (index === 0 && stage.groups.length === 1) {
className = 'no-margin';
} else if (index > 0) {
// If it is not the first column
className = 'left-margin';
}
return className;
},
refreshPipelineGraph() {
this.$emit('refreshPipelineGraph');
},
/**
* CSS class is applied:
* - if pipeline graph contains only one stage column component
*
* @param {number} index
* @returns {boolean}
*/
shouldAddRightMargin(index) {
return !(index === this.graph.length - 1);
},
},
};

View File

@ -1,7 +1,6 @@
@import './pages/admin';
@import './pages/alert_management/details';
@import './pages/alert_management/severity-icons';
@import './pages/boards';
@import './pages/branches';
@import './pages/builds';
@import './pages/ci_projects';

View File

@ -1,3 +1,5 @@
@import 'mixins_and_variables_and_functions';
.user-can-drag {
cursor: grab;
}
@ -356,8 +358,6 @@
}
.avatar {
margin: 0;
@include media-breakpoint-down(md) {
width: $gl-padding;
height: $gl-padding;

View File

@ -0,0 +1,8 @@
.user-can-drag {
cursor: grab;
}
.is-ghost {
opacity: 0.3;
pointer-events: none;
}

View File

@ -2,10 +2,14 @@
# PagesDeployment stores a zip archive containing GitLab Pages web-site
class PagesDeployment < ApplicationRecord
include FileStoreMounter
belongs_to :project, optional: false
belongs_to :ci_build, class_name: 'Ci::Build', optional: true
validates :file, presence: true
validates :file_store, presence: true, inclusion: { in: ObjectStorage::SUPPORTED_STORES }
validates :size, presence: true, numericality: { greater_than: 0, only_integer: true }
mount_file_store_uploader ::Pages::DeploymentUploader
end

View File

@ -27,10 +27,7 @@ class BasePolicy < DeclarativePolicy::Base
desc "User email is unconfirmed or user account is locked"
with_options scope: :user, score: 0
condition(:inactive) do
Feature.enabled?(:inactive_policy_condition, default_enabled: true) &&
@user&.confirmation_required_on_sign_in? || @user&.access_locked?
end
condition(:inactive) { @user&.confirmation_required_on_sign_in? || @user&.access_locked? }
with_options scope: :user, score: 0
condition(:external_user) { @user.nil? || @user.external? }

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module Pages
class DeploymentUploader < GitlabUploader
include ObjectStorage::Concern
storage_options Gitlab.config.pages
alias_method :upload, :model
private
def dynamic_segment
Gitlab::HashedPath.new('pages_deployments', model.id, root_hash: model.project_id)
end
# @hashed is chosen to avoid conflict with namespace name because we use the same directory for storage
# @ is not valid character for namespace
def base_dir
"@hashed"
end
end
end

View File

@ -2,6 +2,7 @@
- page_title _("Issues")
- new_issue_email = @project.new_issuable_address(current_user, 'issue')
= stylesheet_link_tag 'page_bundles/issues'
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues")

View File

@ -10,6 +10,7 @@
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
- can_create_issue = show_new_issue_link?(@project)
- related_branches_path = related_branches_project_issue_path(@project, @issue)
= stylesheet_link_tag 'page_bundles/issues'
= render_if_exists "projects/issues/alert_blocked", issue: @issue, current_user: current_user
= render "projects/issues/alert_moved_from_service_desk", issue: @issue

View File

@ -8,6 +8,7 @@
- @content_class = "issue-boards-content js-focus-mode-board"
- breadcrumb_title _("Issue Boards")
- page_title("#{board.name}", _("Boards"))
= stylesheet_link_tag 'page_bundles/boards'
- content_for :page_specific_javascripts do

View File

@ -36,14 +36,14 @@ class IssuePlacementWorker
Gitlab::ErrorTracking.log_exception(e, issue_id: issue_id, project_id: project_id)
IssueRebalancingWorker.perform_async(nil, project_id.presence || issue.project_id)
end
# rubocop: enable CodeReuse/ActiveRecord
def find_issue(issue_id, project_id)
return Issue.id_in(issue_id).first if issue_id
return Issue.id_in(issue_id).take if issue_id
project = Project.id_in(project_id).first
project = Project.id_in(project_id).take
return unless project
project.issues.first
project.issues.take
end
# rubocop: enable CodeReuse/ActiveRecord
end

View File

@ -11,7 +11,8 @@ class IssueRebalancingWorker
return if project_id.nil?
project = Project.find(project_id)
issue = project.issues.first # All issues are equivalent as far as we are concerned
# All issues are equivalent as far as we are concerned
issue = project.issues.take # rubocop: disable CodeReuse/ActiveRecord
IssueRebalancingService.new(issue).execute
rescue ActiveRecord::RecordNotFound, IssueRebalancingService::TooManyIssues => e

View File

@ -0,0 +1,5 @@
---
title: Use Conan recipe as package name in package API
merge_request: 42860
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Performance fix for issue placement
merge_request: 43315
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Remove temporary index for fixing broken CS fingerprints
merge_request: 43126
author:
type: other

View File

@ -184,7 +184,9 @@ module Gitlab
config.assets.precompile << "notify.css"
config.assets.precompile << "mailers/*.css"
config.assets.precompile << "page_bundles/_mixins_and_variables_and_functions.css"
config.assets.precompile << "page_bundles/boards.css"
config.assets.precompile << "page_bundles/ide.css"
config.assets.precompile << "page_bundles/issues.css"
config.assets.precompile << "page_bundles/jira_connect.css"
config.assets.precompile << "page_bundles/todos.css"
config.assets.precompile << "page_bundles/xterm.css"

View File

@ -1,7 +1,7 @@
---
name: disable_metric_dashboard_refresh_rate
introduced_by_url:
rollout_issue_url:
group:
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/229831
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/229841
group: group::health
type: development
default_enabled: false

View File

@ -1,7 +0,0 @@
---
name: inactive_policy_condition
introduced_by_url:
rollout_issue_url:
group:
type: development
default_enabled: true

View File

@ -394,6 +394,14 @@ production: &base
# File that contains the shared secret key for verifying access for gitlab-pages.
# Default is '.gitlab_pages_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_pages_secret
object_store:
enabled: false
remote_directory: pages # The bucket name
connection:
provider: AWS
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: us-east-1
## Mattermost
## For enabling Add to Mattermost button
@ -1318,6 +1326,14 @@ test:
# user: YOUR_USERNAME
pages:
path: tmp/tests/pages
object_store:
enabled: false
remote_directory: pages # The bucket name
connection:
provider: AWS
aws_access_key_id: AWS_ACCESS_KEY_ID
aws_secret_access_key: AWS_SECRET_ACCESS_KEY
region: us-east-1
repositories:
storages:
default:

View File

@ -297,6 +297,10 @@ Settings.pages['external_http'] ||= false unless Settings.pages['external_http']
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_secret')
# We want pages zip archives to be stored on the same directory as old pages hierarchical structure
# this will allow us to easier migrate existing instances with NFS
Settings.pages['storage_path'] = Settings.pages['path']
Settings.pages['object_store'] = ObjectStoreSettings.legacy_parse(Settings.pages['object_store'])
#
# Geo

View File

@ -40,10 +40,3 @@ has_pick_into_stable_label = gitlab.mr_labels.find { |label| label.start_with?('
if gitlab.branch_for_base != "master" && !has_pick_into_stable_label && !helper.security_mr?
warn "Most of the time, merge requests should target `master`. Otherwise, please set the relevant `Pick into X.Y` label."
end
if gitlab.mr_json['title'].length > 72
warn 'The title of this merge request is longer than 72 characters and ' \
'would violate our commit message rules when using the Squash on Merge ' \
'feature. Please consider adjusting the title, or rebase the ' \
"commits manually and don't use Squash on Merge."
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class RemoveTmpContainerScanningIndex < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'tmp_index_for_fixing_inconsistent_vulnerability_occurrences'
disable_ddl_transaction!
def up
remove_concurrent_index_by_name(:vulnerability_occurrences, INDEX_NAME)
end
def down
# report_type: 2 container scanning
add_concurrent_index(:vulnerability_occurrences, :id,
where: "LENGTH(location_fingerprint) = 40 AND report_type = 2",
name: INDEX_NAME)
end
end

View File

@ -0,0 +1 @@
84b272d61f6ab6e9f9f8eb059ba139a3fa0d2f1bbeb337f2e4e7cd4034822b44

View File

@ -21578,8 +21578,6 @@ CREATE INDEX tmp_build_stage_position_index ON ci_builds USING btree (stage_id,
CREATE INDEX tmp_index_for_email_unconfirmation_migration ON emails USING btree (id) WHERE (confirmed_at IS NOT NULL);
CREATE INDEX tmp_index_for_fixing_inconsistent_vulnerability_occurrences ON vulnerability_occurrences USING btree (id) WHERE ((length(location_fingerprint) = 40) AND (report_type = 2));
CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON merge_request_metrics USING btree (merge_request_id);
CREATE UNIQUE INDEX users_security_dashboard_projects_unique_index ON users_security_dashboard_projects USING btree (project_id, user_id);

View File

@ -50,6 +50,19 @@ Example response:
"version": "1.0.3",
"package_type": "npm",
"created_at": "2019-11-27T03:37:38.711Z"
},
{
"id": 3,
"name": "Hello/0.1@mycompany/stable",
"conan_package_name": "Hello",
"version": "0.1",
"package_type": "conan",
"_links": {
"web_path": "/foo/bar/-/packages/3",
"delete_api_path": "https://gitlab.example.com/api/v4/projects/1/packages/3"
},
"created_at": "2029-12-16T20:33:34.316Z",
"tags": []
}
]
```

View File

@ -35,8 +35,7 @@ the `author` field. GitLab team members **should not**.
- [Security fixes](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md)
**must** have a changelog entry, without `merge_request` value
and with `type` set to `security`.
- Any user-facing change **should** have a changelog entry. Example: "GitLab now
uses system fonts for all text."
- Any user-facing change **should** have a changelog entry. This includes both visual changes (regardless of how minor), and changes to the rendered DOM which impact how a screen reader may announce the content.
- Performance improvements **should** have a changelog entry.
- Changes that need to be documented in the Telemetry [Event Dictionary](telemetry/event_dictionary.md)
also require a changelog entry.

View File

@ -14,7 +14,7 @@ but you can change the sort order by clicking the headers in the Alert Managemen
The alert list displays the following information:
![Alert List](./img/alert_list_v13_1.png)
![Alert List](img/alert_list_v13_1.png)
- **Search** - The alert list supports a simple free text search on the title,
description, monitoring tool, and service fields.
@ -31,9 +31,10 @@ The alert list displays the following information:
- **Triggered**: No one has begun investigation.
- **Acknowledged**: Someone is actively investigating the problem.
- **Resolved**: No further work is required.
TIP: **Tip:**
Check out a [live example](https://gitlab.com/gitlab-examples/ops/incident-setup/everyone/tanuki-inc/-/alert_management)
Check out a live example available from the
[`tanuki-inc` project page](https://gitlab-examples-ops-incident-setup-everyone-tanuki-inc.34.69.64.147.nip.io/)
in GitLab to examine alerts in action.
## Enable Alerts
@ -52,8 +53,7 @@ to view the list of alerts.
You can install the GitLab-managed Prometheus application on your Kubernetes
cluster. For more information, read
[Managed Prometheus on Kubernetes](../../user/project/integrations/prometheus.md#managed-prometheus-on-kubernetes).
When GitLab-managed Prometheus is installed, the [Alerts list](alerts.md)
is also enabled.
When GitLab-managed Prometheus is installed, the Alerts list is also enabled.
To populate the alerts with data, read
[GitLab-Managed Prometheus instances](../metrics/alerts.md#managed-prometheus-instances).
@ -62,7 +62,7 @@ To populate the alerts with data, read
You can configure an externally-managed Prometheus instance to send alerts
to GitLab. To set up this configuration, read the [configuring Prometheus](../metrics/alerts.md#external-prometheus-instances) documentation. Activating the external Prometheus
configuration also enables the [Alerts list](./alerts.md).
configuration also enables the Alerts list.
To populate the alerts with data, read
[External Prometheus instances](../metrics/alerts.md#external-prometheus-instances).
@ -73,9 +73,9 @@ GitLab provides the Generic Alerts endpoint so you can accept alerts from a thir
alerts service. Read the
[instructions for toggling generic alerts](generic_alerts.md#setting-up-generic-alerts)
to add this option. After configuring the endpoint, the
[Alerts list](./alerts.md) is enabled.
Alerts list is enabled.
To populate the alerts with data, read [Customizing the payload](./generic_alerts.md#customizing-the-payload) for requests to the alerts endpoint.
To populate the alerts with data, read [Customizing the payload](generic_alerts.md#customizing-the-payload) for requests to the alerts endpoint.
### Opsgenie integration **(PREMIUM)**
@ -86,7 +86,7 @@ A new way of monitoring Alerts via a GitLab integration is with
NOTE: **Note:**
If you enable the Opsgenie integration, you can't have other GitLab alert services,
such as [Generic Alerts](./generic_alerts.md) or
such as [Generic Alerts](generic_alerts.md) or
Prometheus alerts, active at the same time.
To enable Opsgenie integration:
@ -108,7 +108,7 @@ Each level of alert contains a uniquely shaped and color-coded icon to help
you identify the severity of a particular alert. These severity icons help you
immediately identify which alerts you should prioritize investigating:
![Alert Management Severity System](./img/alert_management_severity_v13_0.png)
![Alert Management Severity System](img/alert_management_severity_v13_0.png)
Alerts contain one of the following icons:

View File

@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
While no configuration is required to use the [manual features](#create-an-incident-manually)
of incident management, some simple [configuration](#configure-incidents) is needed to automate incident creation.
For users with at least Developer [permissions](../../user/permissions.md), the
For users with at least Reporter [permissions](../../user/permissions.md), the
Incident Management list is available at **Operations > Incidents**
in your project's sidebar. The list contains the following metrics:
@ -28,7 +28,8 @@ in your project's sidebar. The list contains the following metrics:
- **{severity-unknown}** **Unknown**
NOTE: **Note:**
Editing incident severity on the incident details page was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229402) in GitLab 13.4.
Editing incident severity on the incident details page was
[introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229402) in GitLab 13.4.
- **Incident** - The description of the incident, which attempts to capture the
most meaningful data.

View File

@ -7,7 +7,19 @@ module API
extend ::API::Entities::EntityHelpers
expose :id
expose :name
expose :name do |package|
if package.conan?
package.conan_recipe
else
package.name
end
end
expose :conan_package_name, if: ->(package) { package.conan? } do |package|
package.name
end
expose :version
expose :package_type

View File

@ -16,7 +16,7 @@ module Banzai
end
def nodes_visible_to_user(user, nodes)
groups = lazy { grouped_objects_for_nodes(nodes, Group, GROUP_ATTR) }
groups = lazy { grouped_objects_for_nodes(nodes, references_relation, GROUP_ATTR) }
nodes.select do |node|
node.has_attribute?(GROUP_ATTR) && can_read_group_reference?(node, user, groups)

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
module UserMentions
module Lib
module Banzai
# isolated Banzai::ReferenceParser
module ReferenceParser
# Returns the reference parser class for the given type
#
# Example:
#
# Banzai::ReferenceParser['isolated_mentioned_group']
#
# This would return the `::Gitlab::BackgroundMigration::UserMentions::Lib::Banzai::ReferenceParser::IsolatedMentionedGroupParser` class.
def self.[](name)
const_get("::Gitlab::BackgroundMigration::UserMentions::Lib::Banzai::ReferenceParser::#{name.to_s.camelize}Parser", false)
end
end
end
end
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
module UserMentions
module Lib
module Banzai
module ReferenceParser
# isolated Banzai::ReferenceParser::MentionedGroupParser
class IsolatedMentionedGroupParser < ::Banzai::ReferenceParser::MentionedGroupParser
extend ::Gitlab::Utils::Override
self.reference_type = :user
override :references_relation
def references_relation
::Gitlab::BackgroundMigration::UserMentions::Models::Group
end
end
end
end
end
end
end
end

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
module UserMentions
module Lib
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class IsolatedReferenceExtractor < ::Gitlab::ReferenceExtractor
REFERABLES = %i(isolated_mentioned_group).freeze
REFERABLES.each do |type|
define_method("#{type}s") do
@references[type] ||= isolated_references(type)
end
end
def isolated_references(type)
context = ::Banzai::RenderContext.new(project, current_user)
processor = ::Gitlab::BackgroundMigration::UserMentions::Lib::Banzai::ReferenceParser[type].new(context)
refs = processor.process(html_documents)
refs[:visible]
end
end
end
end
end
end
end

View File

@ -36,7 +36,8 @@ module Gitlab
if extractor
extractors[current_user] = extractor
else
extractor = extractors[current_user] ||= ::Gitlab::ReferenceExtractor.new(project, current_user)
extractor = extractors[current_user] ||=
Gitlab::BackgroundMigration::UserMentions::Lib::Gitlab::IsolatedReferenceExtractor.new(project, current_user)
extractor.reset_memoized_values
end
@ -71,7 +72,7 @@ module Gitlab
mentioned_users_ids = array_to_sql(refs.mentioned_users.pluck(:id))
mentioned_projects_ids = array_to_sql(refs.mentioned_projects.pluck(:id))
mentioned_groups_ids = array_to_sql(refs.mentioned_groups.pluck(:id))
mentioned_groups_ids = array_to_sql(refs.isolated_mentioned_groups.pluck(:id))
return if mentioned_users_ids.blank? && mentioned_projects_ids.blank? && mentioned_groups_ids.blank?

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
module UserMentions
module Models
module Concerns
module Namespace
# extracted methods for recursive traversing of namespace hierarchy
module RecursiveTraversal
extend ActiveSupport::Concern
def root_ancestor
return self if persisted? && parent_id.nil?
strong_memoize(:root_ancestor) do
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
.base_and_ancestors
.reorder(nil)
.find_by(parent_id: nil)
end
end
# Returns all ancestors, self, and descendants of the current namespace.
def self_and_hierarchy
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
.all_objects
end
# Returns all the ancestors of the current namespaces.
def ancestors
return self.class.none unless parent_id
Gitlab::ObjectHierarchy
.new(self.class.where(id: parent_id))
.base_and_ancestors
end
# returns all ancestors upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
def ancestors_upto(top = nil, hierarchy_order: nil)
Gitlab::ObjectHierarchy.new(self.class.where(id: id))
.ancestors(upto: top, hierarchy_order: hierarchy_order)
end
def self_and_ancestors(hierarchy_order: nil)
return self.class.where(id: id) unless parent_id
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
.base_and_ancestors(hierarchy_order: hierarchy_order)
end
# Returns all the descendants of the current namespace.
def descendants
Gitlab::ObjectHierarchy
.new(self.class.where(parent_id: id))
.base_and_descendants
end
def self_and_descendants
Gitlab::ObjectHierarchy
.new(self.class.where(id: id))
.base_and_descendants
end
end
end
end
end
end
end
end

View File

@ -0,0 +1,91 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
module UserMentions
module Models
# isolated Group model
class Group < ::Gitlab::BackgroundMigration::UserMentions::Models::Namespace
self.store_full_sti_class = false
has_one :saml_provider
def self.declarative_policy_class
"GroupPolicy"
end
def max_member_access_for_user(user)
return GroupMember::NO_ACCESS unless user
return GroupMember::OWNER if user.admin?
max_member_access = members_with_parents.where(user_id: user)
.reorder(access_level: :desc)
.first
&.access_level
max_member_access || GroupMember::NO_ACCESS
end
def members_with_parents
# Avoids an unnecessary SELECT when the group has no parents
source_ids =
if has_parent?
self_and_ancestors.reorder(nil).select(:id)
else
id
end
group_hierarchy_members = GroupMember.active_without_invites_and_requests
.where(source_id: source_ids)
GroupMember.from_union([group_hierarchy_members,
members_from_self_and_ancestor_group_shares])
end
# rubocop: disable Metrics/AbcSize
def members_from_self_and_ancestor_group_shares
group_group_link_table = GroupGroupLink.arel_table
group_member_table = GroupMember.arel_table
source_ids =
if has_parent?
self_and_ancestors.reorder(nil).select(:id)
else
id
end
group_group_links_query = GroupGroupLink.where(shared_group_id: source_ids)
cte = Gitlab::SQL::CTE.new(:group_group_links_cte, group_group_links_query)
cte_alias = cte.table.alias(GroupGroupLink.table_name)
# Instead of members.access_level, we need to maximize that access_level at
# the respective group_group_links.group_access.
member_columns = GroupMember.attribute_names.map do |column_name|
if column_name == 'access_level'
smallest_value_arel([cte_alias[:group_access], group_member_table[:access_level]],
'access_level')
else
group_member_table[column_name]
end
end
GroupMember
.with(cte.to_arel)
.select(*member_columns)
.from([group_member_table, cte.alias_to(group_group_link_table)])
.where(group_member_table[:requested_at].eq(nil))
.where(group_member_table[:source_id].eq(group_group_link_table[:shared_with_group_id]))
.where(group_member_table[:source_type].eq('Namespace'))
end
# rubocop: enable Metrics/AbcSize
def smallest_value_arel(args, column_alias)
Arel::Nodes::As.new(
Arel::Nodes::NamedFunction.new('LEAST', args),
Arel::Nodes::SqlLiteral.new(column_alias))
end
end
end
end
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
module UserMentions
module Models
# isolated Namespace model
class Namespace < ApplicationRecord
include ::Gitlab::VisibilityLevel
include ::Gitlab::Utils::StrongMemoize
include Gitlab::BackgroundMigration::UserMentions::Models::Concerns::Namespace::RecursiveTraversal
belongs_to :parent, class_name: "::Gitlab::BackgroundMigration::UserMentions::Models::Namespace"
def visibility_level_field
:visibility_level
end
def has_parent?
parent_id.present? || parent.present?
end
# Overridden in EE::Namespace
def feature_available?(_feature)
false
end
end
end
end
end
end
Namespace.prepend_if_ee('::EE::Namespace')

View File

@ -90,12 +90,17 @@ module Gitlab
def load_dev_server_manifest
host = ::Rails.configuration.webpack.dev_server.host
port = ::Rails.configuration.webpack.dev_server.port
http = Net::HTTP.new(host, port)
http.use_ssl = ::Rails.configuration.webpack.dev_server.https
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.get(dev_server_path).body
scheme = ::Rails.configuration.webpack.dev_server.https ? 'https' : 'http'
uri = Addressable::URI.new(scheme: scheme, host: host, port: port, path: dev_server_path)
# localhost could be blocked via Gitlab::HTTP
response = HTTParty.get(uri.to_s, verify: false) # rubocop:disable Gitlab/HTTParty
return response.body if response.code == 200
raise "HTTP error #{response.code}"
rescue => e
raise ManifestLoadError.new("Could not load manifest from webpack-dev-server at http://#{host}:#{port}#{dev_server_path} - is it running, and is stats-webpack-plugin loaded?", e)
raise ManifestLoadError.new("Could not load manifest from webpack-dev-server at #{uri} - is it running, and is stats-webpack-plugin loaded?", e)
end
def load_static_manifest

View File

@ -21528,6 +21528,9 @@ msgstr ""
msgid "RepositoriesAnalytics|Test Code Coverage"
msgstr ""
msgid "RepositoriesAnalytics|There was an error fetching the projects."
msgstr ""
msgid "Repository"
msgstr ""

View File

@ -4,9 +4,12 @@ FactoryBot.define do
factory :pages_deployment, class: 'PagesDeployment' do
project
file_store { ObjectStorage::SUPPORTED_STORES.first }
size { 1.megabytes }
# TODO: replace with proper file uploaded in https://gitlab.com/gitlab-org/gitlab/-/issues/245295
file { "dummy string" }
after(:build) do |deployment, _evaluator|
deployment.file = fixture_file_upload(
Rails.root.join("spec/fixtures/pages.zip")
)
deployment.size = deployment.file.size
end
end
end

View File

@ -48,7 +48,7 @@ RSpec.describe 'Group Packages' do
it 'allows you to navigate to the project page' do
page.within('[data-qa-selector="packages-table"]') do
click_link project.name
find('[data-qa-selector="package-path"]', text: project.name).click
end
expect(page).to have_current_path(project_path(project))

View File

@ -11,6 +11,9 @@
"name": {
"type": "string"
},
"conan_package_name": {
"type": "string"
},
"version": {
"type": "string"
},

View File

@ -1,4 +1,4 @@
/* eslint-disable import/no-commonjs */
/* eslint-disable import/no-commonjs, max-classes-per-file */
const path = require('path');
const { ErrorWithStack } = require('jest-util');
@ -58,6 +58,14 @@ class CustomEnvironment extends JSDOMEnvironment {
measure: () => null,
getEntriesByName: () => [],
});
this.global.PerformanceObserver = class {
/* eslint-disable no-useless-constructor, no-unused-vars, no-empty-function, class-methods-use-this */
constructor(callback) {}
disconnect() {}
observe(element, initObject) {}
/* eslint-enable no-useless-constructor, no-unused-vars, no-empty-function, class-methods-use-this */
};
}
async teardown() {

View File

@ -3,23 +3,27 @@ import { mount } from '@vue/test-utils';
import { setHTMLFixture } from 'helpers/fixtures';
import PipelineStore from '~/pipelines/stores/pipeline_store';
import graphComponent from '~/pipelines/components/graph/graph_component.vue';
import stageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue';
import linkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue';
import graphJSON from './mock_data';
import linkedPipelineJSON from './linked_pipelines_mock_data';
import PipelinesMediator from '~/pipelines/pipeline_details_mediator';
describe('graph component', () => {
const store = new PipelineStore();
store.storePipeline(linkedPipelineJSON);
const mediator = new PipelinesMediator({ endpoint: '' });
let store;
let mediator;
let wrapper;
const findExpandPipelineBtn = () => wrapper.find('[data-testid="expandPipelineButton"]');
const findAllExpandPipelineBtns = () => wrapper.findAll('[data-testid="expandPipelineButton"]');
const findStageColumns = () => wrapper.findAll(StageColumnComponent);
const findStageColumnAt = i => findStageColumns().at(i);
beforeEach(() => {
mediator = new PipelinesMediator({ endpoint: '' });
store = new PipelineStore();
store.storePipeline(linkedPipelineJSON);
setHTMLFixture('<div class="layout-page"></div>');
});
@ -43,7 +47,7 @@ describe('graph component', () => {
});
describe('with data', () => {
it('should render the graph', () => {
beforeEach(() => {
wrapper = mount(graphComponent, {
propsData: {
isLoading: false,
@ -51,26 +55,17 @@ describe('graph component', () => {
mediator,
},
});
});
it('renders the graph', () => {
expect(wrapper.find('.js-pipeline-graph').exists()).toBe(true);
expect(wrapper.find(stageColumnComponent).classes()).toContain('no-margin');
expect(
wrapper
.findAll(stageColumnComponent)
.at(1)
.classes(),
).toContain('left-margin');
expect(wrapper.find('.stage-column:nth-child(2) .build:nth-child(1)').classes()).toContain(
'left-connector',
);
expect(wrapper.find('.loading-icon').exists()).toBe(false);
expect(wrapper.find('.stage-column-list').exists()).toBe(true);
});
it('renders columns in the graph', () => {
expect(findStageColumns()).toHaveLength(graphJSON.details.stages.length);
});
});
describe('when linked pipelines are present', () => {
@ -93,26 +88,26 @@ describe('graph component', () => {
expect(wrapper.find('.fa-spinner').exists()).toBe(false);
});
it('should include the stage column list', () => {
expect(wrapper.find(stageColumnComponent).exists()).toBe(true);
it('should include the stage column', () => {
expect(findStageColumnAt(0).exists()).toBe(true);
});
it('should include the no-margin class on the first child if there is only one job', () => {
const firstStageColumnElement = wrapper.find(stageColumnComponent);
expect(firstStageColumnElement.classes()).toContain('no-margin');
});
it('should include the has-only-one-job class on the first child', () => {
const firstStageColumnElement = wrapper.find('.stage-column-list .stage-column');
expect(firstStageColumnElement.classes()).toContain('has-only-one-job');
it('stage column should have no-margin, gl-mr-26, has-only-one-job classes if there is only one job', () => {
expect(findStageColumnAt(0).classes()).toEqual(
expect.arrayContaining(['no-margin', 'gl-mr-26', 'has-only-one-job']),
);
});
it('should include the left-margin class on the second child', () => {
const firstStageColumnElement = wrapper.find('.stage-column-list .stage-column:last-child');
expect(findStageColumnAt(1).classes('left-margin')).toBe(true);
});
expect(firstStageColumnElement.classes()).toContain('left-margin');
it('should include the left-connector class in the build of the second child', () => {
expect(
findStageColumnAt(1)
.find('.build:nth-child(1)')
.classes('left-connector'),
).toBe(true);
});
it('should include the js-has-linked-pipelines flag', () => {
@ -134,12 +129,7 @@ describe('graph component', () => {
describe('stageConnectorClass', () => {
it('it returns left-margin when there is a triggerer', () => {
expect(
wrapper
.findAll(stageColumnComponent)
.at(1)
.classes(),
).toContain('left-margin');
expect(findStageColumnAt(1).classes('left-margin')).toBe(true);
});
});
});
@ -248,6 +238,16 @@ describe('graph component', () => {
.catch(done.fail);
});
});
describe('when column requests a refresh', () => {
beforeEach(() => {
findStageColumnAt(0).vm.$emit('refreshPipelineGraph');
});
it('refreshPipelineGraph is emitted', () => {
expect(wrapper.emitted().refreshPipelineGraph).toHaveLength(1);
});
});
});
});
});
@ -268,7 +268,7 @@ describe('graph component', () => {
it('should include the first column with a no margin', () => {
const firstColumn = wrapper.find('.stage-column');
expect(firstColumn.classes()).toContain('no-margin');
expect(firstColumn.classes('no-margin')).toBe(true);
});
it('should not render a linked pipelines column', () => {
@ -278,16 +278,11 @@ describe('graph component', () => {
describe('stageConnectorClass', () => {
it('it returns no-margin when no triggerer and there is one job', () => {
expect(wrapper.find(stageColumnComponent).classes()).toContain('no-margin');
expect(findStageColumnAt(0).classes('no-margin')).toBe(true);
});
it('it returns left-margin when no triggerer and not the first stage', () => {
expect(
wrapper
.findAll(stageColumnComponent)
.at(1)
.classes(),
).toContain('left-margin');
expect(findStageColumnAt(1).classes('left-margin')).toBe(true);
});
});
});
@ -302,12 +297,9 @@ describe('graph component', () => {
},
});
expect(
wrapper
.find('.stage-column:nth-child(2) .stage-name')
.text()
.trim(),
).toEqual('Deploy &lt;img src=x onerror=alert(document.domain)&gt;');
expect(findStageColumnAt(1).props('title')).toEqual(
'Deploy &lt;img src=x onerror=alert(document.domain)&gt;',
);
});
});
});

View File

@ -74,14 +74,14 @@ RSpec.describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMent
let(:user_mentions) { merge_request_user_mentions }
let(:resource) { merge_request }
it_behaves_like 'resource mentions migration', MigrateMergeRequestMentionsToDb, MergeRequest
it_behaves_like 'resource mentions migration', MigrateMergeRequestMentionsToDb, 'MergeRequest'
context 'when FF disabled' do
before do
stub_feature_flags(migrate_user_mentions: false)
end
it_behaves_like 'resource migration not run', MigrateMergeRequestMentionsToDb, MergeRequest
it_behaves_like 'resource migration not run', MigrateMergeRequestMentionsToDb, 'MergeRequest'
end
end
@ -103,14 +103,14 @@ RSpec.describe Gitlab::BackgroundMigration::UserMentions::CreateResourceUserMent
let(:user_mentions) { commit_user_mentions }
let(:resource) { commit }
it_behaves_like 'resource notes mentions migration', MigrateCommitNotesMentionsToDb, Commit
it_behaves_like 'resource notes mentions migration', MigrateCommitNotesMentionsToDb, 'Commit'
context 'when FF disabled' do
before do
stub_feature_flags(migrate_user_mentions: false)
end
it_behaves_like 'resource notes migration not run', MigrateCommitNotesMentionsToDb, Commit
it_behaves_like 'resource notes migration not run', MigrateCommitNotesMentionsToDb, 'Commit'
end
end
end

View File

@ -229,12 +229,6 @@ RSpec.describe GlobalPolicy do
it { is_expected.not_to be_allowed(:access_api) }
end
it 'when `inactive_policy_condition` feature flag is turned off' do
stub_feature_flags(inactive_policy_condition: false)
is_expected.to be_allowed(:access_api)
end
end
end
@ -321,12 +315,6 @@ RSpec.describe GlobalPolicy do
end
it { is_expected.not_to be_allowed(:access_git) }
it 'when `inactive_policy_condition` feature flag is turned off' do
stub_feature_flags(inactive_policy_condition: false)
is_expected.to be_allowed(:access_git)
end
end
context 'when terms are enforced' do
@ -403,12 +391,6 @@ RSpec.describe GlobalPolicy do
end
it { is_expected.not_to be_allowed(:use_slash_commands) }
it 'when `inactive_policy_condition` feature flag is turned off' do
stub_feature_flags(inactive_policy_condition: false)
is_expected.to be_allowed(:use_slash_commands)
end
end
context 'when access locked' do

View File

@ -23,6 +23,19 @@ RSpec.describe API::ProjectPackages do
it_behaves_like 'returns packages', :project, :no_type
end
context 'with conan package' do
let!(:conan_package) { create(:conan_package, project: project) }
it 'uses the conan recipe as the package name' do
subject
response_conan_package = json_response.find { |package| package['id'] == conan_package.id }
expect(response_conan_package['name']).to eq(conan_package.conan_recipe)
expect(response_conan_package['conan_package_name']).to eq(conan_package.name)
end
end
context 'project is private' do
let(:project) { create(:project, :private) }

View File

@ -89,6 +89,13 @@ module StubObjectStorage
**params)
end
def stub_pages_object_storage(uploader = described_class, **params)
stub_object_storage_uploader(config: Gitlab.config.pages.object_store,
uploader: uploader,
remote_directory: 'pages',
**params)
end
def stub_object_storage_multipart_init(endpoint, upload_id = "upload_id")
stub_request(:post, %r{\A#{endpoint}tmp/uploads/[a-z0-9-]*\?uploads\z})
.to_return status: 200, body: <<-EOS.strip_heredoc

View File

@ -1,12 +1,13 @@
# frozen_string_literal: true
RSpec.shared_examples 'resource mentions migration' do |migration_class, resource_class|
RSpec.shared_examples 'resource mentions migration' do |migration_class, resource_class_name|
it 'migrates resource mentions' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
resource_class = "#{Gitlab::BackgroundMigration::UserMentions::Models}::#{resource_class_name}".constantize
expect do
subject.perform(resource_class.name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
subject.perform(resource_class_name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
end.to change { user_mentions.count }.by(1)
user_mention = user_mentions.last
@ -16,23 +17,23 @@ RSpec.shared_examples 'resource mentions migration' do |migration_class, resourc
# check that performing the same job twice does not fail and does not change counts
expect do
subject.perform(resource_class.name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
subject.perform(resource_class_name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
end.to change { user_mentions.count }.by(0)
end
end
RSpec.shared_examples 'resource notes mentions migration' do |migration_class, resource_class|
RSpec.shared_examples 'resource notes mentions migration' do |migration_class, resource_class_name|
it 'migrates mentions from note' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
# there are 5 notes for each noteable_type, but two do not have mentions and
# another one's noteable_id points to an inexistent resource
expect(notes.where(noteable_type: resource_class.to_s).count).to eq 5
expect(notes.where(noteable_type: resource_class_name).count).to eq 5
expect(user_mentions.count).to eq 0
expect do
subject.perform(resource_class.name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
subject.perform(resource_class_name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
end.to change { user_mentions.count }.by(2)
# check that the user_mention for regular note is created
@ -51,7 +52,7 @@ RSpec.shared_examples 'resource notes mentions migration' do |migration_class, r
# check that performing the same job twice does not fail and does not change counts
expect do
subject.perform(resource_class.name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
subject.perform(resource_class_name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
end.to change { user_mentions.count }.by(0)
end
end
@ -83,24 +84,25 @@ RSpec.shared_examples 'schedules resource mentions migration' do |resource_class
end
end
RSpec.shared_examples 'resource migration not run' do |migration_class, resource_class|
RSpec.shared_examples 'resource migration not run' do |migration_class, resource_class_name|
it 'does not migrate mentions' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
resource_class = "#{Gitlab::BackgroundMigration::UserMentions::Models}::#{resource_class_name}".constantize
expect do
subject.perform(resource_class_name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
end.to change { user_mentions.count }.by(0)
end
end
RSpec.shared_examples 'resource notes migration not run' do |migration_class, resource_class_name|
it 'does not migrate mentions' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
expect do
subject.perform(resource_class.name, join, conditions, false, resource_class.minimum(:id), resource_class.maximum(:id))
end.to change { user_mentions.count }.by(0)
end
end
RSpec.shared_examples 'resource notes migration not run' do |migration_class, resource_class|
it 'does not migrate mentions' do
join = migration_class::JOIN
conditions = migration_class::QUERY_CONDITIONS
expect do
subject.perform(resource_class.name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
subject.perform(resource_class_name, join, conditions, true, Note.minimum(:id), Note.maximum(:id))
end.to change { user_mentions.count }.by(0)
end
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Pages::DeploymentUploader do
let(:pages_deployment) { create(:pages_deployment) }
let(:uploader) { described_class.new(pages_deployment, :file) }
subject { uploader }
it_behaves_like "builds correct paths",
store_dir: %r[/\h{2}/\h{2}/\h{64}/pages_deployments/\d+],
cache_dir: %r[pages/@hashed/tmp/cache],
work_dir: %r[pages/@hashed/tmp/work]
context 'when object store is REMOTE' do
before do
stub_pages_object_storage
end
include_context 'with storage', described_class::Store::REMOTE
it_behaves_like 'builds correct paths', store_dir: %r[\A\h{2}/\h{2}/\h{64}/pages_deployments/\d+\z]
end
context 'when file is stored in valid local_path' do
let(:file) do
fixture_file_upload("spec/fixtures/pages.zip")
end
before do
uploader.store!(file)
end
subject { uploader.file.path }
it { is_expected.to match(%r[#{uploader.root}/@hashed/\h{2}/\h{2}/\h{64}/pages_deployments/#{pages_deployment.id}/pages.zip]) }
end
end