Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c15582526d
commit
c86ec1d072
|
@ -717,7 +717,6 @@ Gitlab/RailsLogger:
|
|||
- 'spec/**/*.rb'
|
||||
- 'ee/spec/**/*.rb'
|
||||
|
||||
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/267606
|
||||
RSpec/FactoryBot/InlineAssociation:
|
||||
Include:
|
||||
- 'spec/factories/**/*.rb'
|
||||
|
|
|
@ -53,9 +53,10 @@ export const initCopyCodeButton = (selector = '#content-body') => {
|
|||
customElements.define('copy-code', CopyCodeButton);
|
||||
}
|
||||
|
||||
const exclude = document.querySelector('.file-content.code'); // this behavior is not needed when viewing raw file content, so excluding it as the unnecessary dom lookups can become expensive
|
||||
const el = document.querySelector(selector);
|
||||
|
||||
if (!el) return () => {};
|
||||
if (!el || exclude) return () => {};
|
||||
|
||||
const observer = new MutationObserver(() => addCodeButton());
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
function addBlameLink(containerSelector, linkClass) {
|
||||
import { getPageParamValue, getPageSearchString } from './utils';
|
||||
|
||||
export function addBlameLink(containerSelector, linkClass) {
|
||||
const containerEl = document.querySelector(containerSelector);
|
||||
|
||||
if (!containerEl) {
|
||||
|
@ -13,10 +15,14 @@ function addBlameLink(containerSelector, linkClass) {
|
|||
lineLinkCopy.classList.remove(linkClass, 'diff-line-num');
|
||||
|
||||
const { lineNumber } = lineLink.dataset;
|
||||
const { blamePath } = document.querySelector('.line-numbers').dataset;
|
||||
const blameLink = document.createElement('a');
|
||||
const { blamePath } = document.querySelector('.line-numbers').dataset;
|
||||
blameLink.classList.add('file-line-blame');
|
||||
blameLink.href = `${blamePath}#L${lineNumber}`;
|
||||
const blamePerPage = document.querySelector('.js-per-page')?.dataset?.blamePerPage;
|
||||
const pageNumber = getPageParamValue(lineNumber, blamePerPage);
|
||||
const searchString = getPageSearchString(blamePath, pageNumber);
|
||||
|
||||
blameLink.href = `${blamePath}${searchString}#L${lineNumber}`;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.classList.add('line-links', 'diff-line-num');
|
||||
|
@ -27,5 +33,3 @@ function addBlameLink(containerSelector, linkClass) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default addBlameLink;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { getLocationHash } from '../lib/utils/url_utility';
|
||||
import { getPageParamValue, getPageSearchString } from './utils';
|
||||
|
||||
const lineNumberRe = /^(L|LC)[0-9]+/;
|
||||
|
||||
|
@ -16,7 +17,10 @@ const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
|
|||
permalinkButton.dataset.originalHref = href;
|
||||
return href;
|
||||
})();
|
||||
permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
|
||||
const lineNum = parseInt(hash.split('L')[1], 10);
|
||||
const page = getPageParamValue(lineNum);
|
||||
const searchString = getPageSearchString(baseHref, page);
|
||||
permalinkButton.setAttribute('href', `${baseHref}${searchString}${hashUrlString}`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Editor from '~/editor/source_editor';
|
||||
import { getBaseURL } from '~/lib/utils/url_utility';
|
||||
|
||||
export function initSourceEditor({ el, ...args }) {
|
||||
const editor = new Editor({
|
||||
|
@ -13,4 +14,19 @@ export function initSourceEditor({ el, ...args }) {
|
|||
});
|
||||
}
|
||||
|
||||
const blameLinesPerPage = document.querySelector('.js-per-page')?.dataset?.blamePerPage;
|
||||
|
||||
export const getPageParamValue = (lineNum, blamePerPage = blameLinesPerPage) => {
|
||||
if (!blamePerPage) return '';
|
||||
const page = Math.ceil(parseInt(lineNum, 10) / parseInt(blamePerPage, 10));
|
||||
return page <= 1 ? '' : page;
|
||||
};
|
||||
|
||||
export const getPageSearchString = (path, page) => {
|
||||
if (!page) return '';
|
||||
const url = new URL(path, getBaseURL());
|
||||
url.searchParams.set('page', page);
|
||||
return url.search;
|
||||
};
|
||||
|
||||
export default () => ({});
|
||||
|
|
|
@ -14,7 +14,7 @@ import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
|
|||
import CodeIntelligence from '~/code_navigation/components/app.vue';
|
||||
import LineHighlighter from '~/blob/line_highlighter';
|
||||
import blobInfoQuery from 'shared_queries/repository/blob_info.query.graphql';
|
||||
import addBlameLink from '~/blob/blob_blame_link';
|
||||
import { addBlameLink } from '~/blob/blob_blame_link';
|
||||
import projectInfoQuery from '../queries/project_info.query.graphql';
|
||||
import getRefMixin from '../mixins/get_ref';
|
||||
import userInfoQuery from '../queries/user_info.query.graphql';
|
||||
|
|
|
@ -15,8 +15,14 @@ export default {
|
|||
mounted() {
|
||||
handleBlobRichViewer(this.$refs.content, this.type);
|
||||
},
|
||||
safeHtmlConfig: {
|
||||
ADD_TAGS: ['copy-code'],
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<markdown-field-view ref="content" v-safe-html="richViewer || content" />
|
||||
<markdown-field-view
|
||||
ref="content"
|
||||
v-safe-html:[$options.safeHtmlConfig]="richViewer || content"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlIntersectionObserver, GlSafeHtmlDirective } from '@gitlab/ui';
|
||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||
import ChunkLine from './chunk_line.vue';
|
||||
|
||||
/*
|
||||
|
@ -46,6 +47,11 @@ export default {
|
|||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
totalChunks: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -56,11 +62,27 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
lines() {
|
||||
return this.content.split('\n');
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
window.requestIdleCallback(() => {
|
||||
this.isLoading = false;
|
||||
const { hash } = this.$route;
|
||||
if (hash && this.totalChunks > 0 && this.totalChunks === this.chunkIndex + 1) {
|
||||
// when the last chunk is loaded scroll to the hash
|
||||
scrollToElement(hash, { behavior: 'auto' });
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
handleChunkAppear() {
|
||||
if (!this.isHighlighted) {
|
||||
|
@ -85,17 +107,18 @@ export default {
|
|||
:blame-path="blamePath"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="gl-display-flex gl-text-transparent">
|
||||
<div v-else-if="!isLoading" class="gl-display-flex gl-text-transparent">
|
||||
<div class="gl-display-flex gl-flex-direction-column content-visibility-auto">
|
||||
<span
|
||||
v-for="(n, index) in totalLines"
|
||||
v-once
|
||||
:id="`L${calculateLineNumber(index)}`"
|
||||
:key="index"
|
||||
data-testid="line-number"
|
||||
v-text="calculateLineNumber(index)"
|
||||
></span>
|
||||
</div>
|
||||
<div class="gl-white-space-pre-wrap!" data-testid="content" v-text="content"></div>
|
||||
<div v-once class="gl-white-space-pre-wrap!" data-testid="content">{{ content }}</div>
|
||||
</div>
|
||||
</gl-intersection-observer>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlSafeHtmlDirective } from '@gitlab/ui';
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { getPageParamValue, getPageSearchString } from '~/blob/utils';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
|
@ -25,6 +26,13 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
pageSearchString() {
|
||||
if (!this.glFeatures.fileLineBlame) return '';
|
||||
const page = getPageParamValue(this.number);
|
||||
return getPageSearchString(this.blamePath, page);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
|
@ -35,7 +43,7 @@ export default {
|
|||
<a
|
||||
v-if="glFeatures.fileLineBlame"
|
||||
class="gl-user-select-none gl-shadow-none! file-line-blame"
|
||||
:href="`${blamePath}#L${number}`"
|
||||
:href="`${blamePath}${pageSearchString}#L${number}`"
|
||||
></a>
|
||||
<a
|
||||
:id="`L${number}`"
|
||||
|
|
|
@ -65,6 +65,9 @@ export default {
|
|||
!supportedLanguages.includes(this.blob.language?.toLowerCase())
|
||||
);
|
||||
},
|
||||
totalChunks() {
|
||||
return Object.keys(this.chunks).length;
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
addBlobLinksTracking();
|
||||
|
@ -217,6 +220,7 @@ export default {
|
|||
:chunk-index="index"
|
||||
:language="chunk.language"
|
||||
:blame-path="blob.blamePath"
|
||||
:total-chunks="totalChunks"
|
||||
@appear="highlightChunk"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
module Packages
|
||||
module Rpm
|
||||
module RepositoryMetadata
|
||||
class BaseBuilder
|
||||
def initialize(xml: nil, data: {})
|
||||
@xml = Nokogiri::XML(xml) if xml.present?
|
||||
@data = data
|
||||
end
|
||||
|
||||
def execute
|
||||
return build_empty_structure if xml.blank?
|
||||
|
||||
update_xml_document
|
||||
update_package_count
|
||||
xml.to_xml
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :xml, :data
|
||||
|
||||
def build_empty_structure
|
||||
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
||||
xml.method_missing(self.class::ROOT_TAG, self.class::ROOT_ATTRIBUTES)
|
||||
end.to_xml
|
||||
end
|
||||
|
||||
def update_xml_document
|
||||
# Add to the root xml element a new package metadata node
|
||||
xml.at(self.class::ROOT_TAG).add_child(build_new_node)
|
||||
end
|
||||
|
||||
def update_package_count
|
||||
packages_count = xml.css("//#{self.class::ROOT_TAG}/package").count
|
||||
|
||||
xml.at(self.class::ROOT_TAG).attributes["packages"].value = packages_count.to_s
|
||||
end
|
||||
|
||||
def build_new_node
|
||||
raise NotImplementedError, "#{self.class} should implement #{__method__}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
module Packages
|
||||
module Rpm
|
||||
module RepositoryMetadata
|
||||
class BuildFilelistXml < ::Packages::Rpm::RepositoryMetadata::BaseBuilder
|
||||
ROOT_TAG = 'filelists'
|
||||
ROOT_ATTRIBUTES = {
|
||||
xmlns: 'http://linux.duke.edu/metadata/filelists',
|
||||
packages: '0'
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
module Packages
|
||||
module Rpm
|
||||
module RepositoryMetadata
|
||||
class BuildOtherXml < ::Packages::Rpm::RepositoryMetadata::BaseBuilder
|
||||
ROOT_TAG = 'otherdata'
|
||||
ROOT_ATTRIBUTES = {
|
||||
xmlns: 'http://linux.duke.edu/metadata/other',
|
||||
packages: '0'
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
module Packages
|
||||
module Rpm
|
||||
module RepositoryMetadata
|
||||
class BuildOtherXmlService
|
||||
ROOT_TAG = 'otherdata'
|
||||
ROOT_ATTRIBUTES = {
|
||||
xmlns: 'http://linux.duke.edu/metadata/other',
|
||||
packages: '0'
|
||||
}.freeze
|
||||
|
||||
def initialize(data)
|
||||
@data = data
|
||||
end
|
||||
|
||||
def execute
|
||||
builder = Nokogiri::XML::Builder.new do |xml|
|
||||
xml.package(pkgid: data[:pkgid], name: data[:name], arch: data[:arch]) do
|
||||
xml.version epoch: data[:epoch], ver: data[:version], rel: data[:release]
|
||||
build_changelog_nodes(xml)
|
||||
end
|
||||
end
|
||||
|
||||
Nokogiri::XML(builder.to_xml).at('package')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :data
|
||||
|
||||
def build_changelog_nodes(xml)
|
||||
data[:changelogs].each do |changelog|
|
||||
xml.changelog changelog[:changelogtext], date: changelog[:changelogtime]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@
|
|||
module Packages
|
||||
module Rpm
|
||||
module RepositoryMetadata
|
||||
class BuildPrimaryXml < ::Packages::Rpm::RepositoryMetadata::BaseBuilder
|
||||
class BuildPrimaryXmlService
|
||||
ROOT_TAG = 'metadata'
|
||||
ROOT_ATTRIBUTES = {
|
||||
xmlns: 'http://linux.duke.edu/metadata/common',
|
||||
|
@ -11,19 +11,19 @@ module Packages
|
|||
}.freeze
|
||||
|
||||
# Nodes that have only text without attributes
|
||||
REQUIRED_BASE_ATTRIBUTES = %i[name arch summary description].freeze
|
||||
NOT_REQUIRED_BASE_ATTRIBUTES = %i[url packager].freeze
|
||||
BASE_ATTRIBUTES = %i[name arch summary description url packager].freeze
|
||||
FORMAT_NODE_BASE_ATTRIBUTES = %i[license vendor group buildhost sourcerpm].freeze
|
||||
|
||||
private
|
||||
def initialize(data)
|
||||
@data = data
|
||||
end
|
||||
|
||||
def build_new_node
|
||||
def execute
|
||||
builder = Nokogiri::XML::Builder.new do |xml|
|
||||
xml.package(type: :rpm, 'xmlns:rpm': 'http://linux.duke.edu/metadata/rpm') do
|
||||
build_required_base_attributes(xml)
|
||||
build_not_required_base_attributes(xml)
|
||||
build_base_attributes(xml)
|
||||
xml.version epoch: data[:epoch], ver: data[:version], rel: data[:release]
|
||||
xml.checksum data[:checksum], type: 'sha256', pkgid: 'YES'
|
||||
xml.checksum data[:pkgid], type: 'sha256', pkgid: 'YES'
|
||||
xml.size package: data[:packagesize], installed: data[:installedsize], archive: data[:archivesize]
|
||||
xml.time file: data[:filetime], build: data[:buildtime]
|
||||
xml.location href: data[:location] if data[:location].present?
|
||||
|
@ -34,14 +34,12 @@ module Packages
|
|||
Nokogiri::XML(builder.to_xml).at('package')
|
||||
end
|
||||
|
||||
def build_required_base_attributes(xml)
|
||||
REQUIRED_BASE_ATTRIBUTES.each do |attribute|
|
||||
xml.method_missing(attribute, data[attribute])
|
||||
end
|
||||
end
|
||||
private
|
||||
|
||||
def build_not_required_base_attributes(xml)
|
||||
NOT_REQUIRED_BASE_ATTRIBUTES.each do |attribute|
|
||||
attr_reader :data
|
||||
|
||||
def build_base_attributes(xml)
|
||||
BASE_ATTRIBUTES.each do |attribute|
|
||||
xml.method_missing(attribute, data[attribute]) if data[attribute].present?
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@
|
|||
module Packages
|
||||
module Rpm
|
||||
module RepositoryMetadata
|
||||
class BuildRepomdXml
|
||||
class BuildRepomdXmlService
|
||||
attr_reader :data
|
||||
|
||||
ROOT_ATTRIBUTES = {
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
module Packages
|
||||
module Rpm
|
||||
module RepositoryMetadata
|
||||
class UpdateXmlService
|
||||
BUILDERS = {
|
||||
other: ::Packages::Rpm::RepositoryMetadata::BuildOtherXmlService,
|
||||
primary: ::Packages::Rpm::RepositoryMetadata::BuildPrimaryXmlService
|
||||
}.freeze
|
||||
|
||||
def initialize(filename:, xml: nil, data: {})
|
||||
@builder_class = BUILDERS[filename]
|
||||
raise ArgumentError, "Filename must be one of: #{BUILDERS.keys.join(', ')}" if @builder_class.nil?
|
||||
|
||||
@xml = Nokogiri::XML(xml) if xml.present?
|
||||
@data = data
|
||||
@filename = filename
|
||||
end
|
||||
|
||||
def execute
|
||||
return build_empty_structure if xml.blank?
|
||||
|
||||
remove_existing_packages
|
||||
update_xml_document
|
||||
update_package_count
|
||||
xml.to_xml
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :xml, :data, :builder_class, :filename
|
||||
|
||||
def build_empty_structure
|
||||
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
||||
xml.method_missing(builder_class::ROOT_TAG, builder_class::ROOT_ATTRIBUTES)
|
||||
end.to_xml
|
||||
end
|
||||
|
||||
def update_xml_document
|
||||
# Add to the root xml element a new package metadata node
|
||||
xml.at(builder_class::ROOT_TAG).add_child(builder_class.new(data).execute)
|
||||
end
|
||||
|
||||
def update_package_count
|
||||
packages_count = xml.css("//#{builder_class::ROOT_TAG}/package").count
|
||||
|
||||
xml.at(builder_class::ROOT_TAG).attributes["packages"].value = packages_count.to_s
|
||||
end
|
||||
|
||||
def remove_existing_packages
|
||||
case filename
|
||||
when :primary
|
||||
xml.search("checksum:contains('#{data[:pkgid]}')").each { _1.parent&.remove }
|
||||
else
|
||||
xml.search("[pkgid='#{data[:pkgid]}']").each(&:remove)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,7 +6,7 @@
|
|||
- if readme_path = @project.repository.readme_path
|
||||
- add_page_startup_api_call project_blob_path(@project, tree_join(@ref, readme_path), viewer: "rich", format: "json")
|
||||
|
||||
#tree-holder.tree-holder.clearfix
|
||||
#tree-holder.tree-holder.clearfix.js-per-page{ data: { blame_per_page: Projects::BlameService::PER_PAGE } }
|
||||
.nav-block.gl-display-flex.gl-xs-flex-direction-column.gl-align-items-stretch
|
||||
= render 'projects/tree/tree_header', tree: @tree, is_project_overview: is_project_overview
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#js-code-owners{ data: { blob_path: blob.path, project_path: @project.full_path, branch: @ref } }
|
||||
= render "projects/blob/auxiliary_viewer", blob: blob
|
||||
|
||||
#blob-content-holder.blob-content-holder
|
||||
#blob-content-holder.blob-content-holder.js-per-page{ data: { blame_per_page: Projects::BlameService::PER_PAGE } }
|
||||
- if @code_navigation_path
|
||||
#js-code-navigation{ data: { code_navigation_path: @code_navigation_path, blob_path: blob.path, definition_path_prefix: project_blob_path(@project, @ref) } }
|
||||
- if !expanded
|
||||
|
|
|
@ -27,6 +27,7 @@ options:
|
|||
- i_package_pypi_deploy_token
|
||||
- i_package_rubygems_deploy_token
|
||||
- i_package_terraform_module_deploy_token
|
||||
- i_package_rpm_deploy_token
|
||||
distribution:
|
||||
- ee
|
||||
- ce
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: redis_hll_counters.user_packages.i_package_rpm_user_weekly
|
||||
description: A weekly count of users that have published an rpm package to the registry
|
||||
product_section: ops
|
||||
product_stage: package
|
||||
product_group: package
|
||||
product_category: package_registry
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '15.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97133
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_package_rpm_user
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: redis_hll_counters.deploy_token_packages.i_package_rpm_deploy_token_weekly
|
||||
description: A weekly count of RPM packages published to the registry using a deploy token
|
||||
product_section: ops
|
||||
product_stage: package
|
||||
product_group: package
|
||||
product_category: package_registry
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '15.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97133
|
||||
time_frame: 7d
|
||||
data_source: redis_hll
|
||||
instrumentation_class: RedisHLLMetric
|
||||
options:
|
||||
events:
|
||||
- i_package_rpm_deploy_token
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: counts.package_events_i_package_rpm_pull_package
|
||||
description: A count of RPM packages that have been downloaded
|
||||
product_section: ops
|
||||
product_stage: package
|
||||
product_group: package
|
||||
product_category: package_registry
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '15.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97133
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
instrumentation_class: RedisMetric
|
||||
options:
|
||||
prefix: package_events
|
||||
event: i_package_rpm_pull_package
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
data_category: optional
|
||||
key_path: counts.package_events_i_package_rpm_push_package
|
||||
description: A count of RPM packages that have been published
|
||||
product_section: ops
|
||||
product_stage: package
|
||||
product_group: package
|
||||
product_category: package_registry
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: '15.6'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97133
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
instrumentation_class: RedisMetric
|
||||
options:
|
||||
prefix: package_events
|
||||
event: i_package_rpm_push_package
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -174,7 +174,7 @@ For installations from source:
|
|||
RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:lfs:migrate
|
||||
```
|
||||
|
||||
You can optionally track progress and verify that all packages migrated successfully using the
|
||||
You can optionally track progress and verify that all LFS objects migrated successfully using the
|
||||
[PostgreSQL console](https://docs.gitlab.com/omnibus/settings/database.html#connecting-to-the-bundled-postgresql-database):
|
||||
|
||||
- `sudo gitlab-rails dbconsole` for Omnibus GitLab instances.
|
||||
|
|
|
@ -179,7 +179,10 @@ To set issue weight of a task:
|
|||
|
||||
## Add a task to an iteration **(PREMIUM)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/362550) in GitLab 15.5.
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367456) in GitLab 15.5 [with a flag](../administration/feature_flags.md) named `work_items_mvc_2`. Disabled by default.
|
||||
|
||||
FLAG:
|
||||
On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `work_items_mvc_2`. On GitLab.com, this feature is not available. The feature is not ready for production use.
|
||||
|
||||
You can add a task to an [iteration](group/iterations/index.md).
|
||||
You can see the iteration title and period only when you view a task.
|
||||
|
|
|
@ -17,7 +17,7 @@ module API
|
|||
requires :namespace_path, type: String, desc: 'Path for the namespace that should be subscribed'
|
||||
end
|
||||
post do
|
||||
not_found! unless Feature.enabled?(:jira_connect_oauth_self_managed, current_user)
|
||||
not_found! unless Feature.enabled?(:jira_connect_oauth, current_user)
|
||||
|
||||
jwt = Atlassian::JiraConnect::Jwt::Symmetric.new(params[:jwt])
|
||||
installation = JiraConnectInstallation.find_by_client_key(jwt.iss_claim)
|
||||
|
|
|
@ -39,6 +39,13 @@ module API
|
|||
requires :file_name, type: String, desc: 'RPM package file name'
|
||||
end
|
||||
get '*package_file_id/*file_name', requirements: { file_name: API::NO_SLASH_URL_PART_REGEX } do
|
||||
track_package_event(
|
||||
'pull_package',
|
||||
:rpm,
|
||||
category: self.class.name,
|
||||
project: authorized_user_project,
|
||||
namespace: authorized_user_project.namespace
|
||||
)
|
||||
not_found!
|
||||
end
|
||||
|
||||
|
@ -50,6 +57,15 @@ module API
|
|||
bad_request!('File is too large')
|
||||
end
|
||||
|
||||
track_package_event(
|
||||
'push_package',
|
||||
:rpm,
|
||||
user: current_user,
|
||||
category: self.class.name,
|
||||
project: authorized_user_project,
|
||||
namespace: authorized_user_project.namespace
|
||||
)
|
||||
|
||||
not_found!
|
||||
end
|
||||
|
||||
|
|
|
@ -976,6 +976,24 @@ module Gitlab
|
|||
rescue ArgumentError
|
||||
end
|
||||
|
||||
# Remove any instances of deprecated job classes lingering in queues.
|
||||
#
|
||||
# rubocop:disable Cop/SidekiqApiUsage
|
||||
def sidekiq_remove_jobs(job_klass:)
|
||||
Sidekiq::Queue.new(job_klass.queue).each do |job|
|
||||
job.delete if job.klass == job_klass.to_s
|
||||
end
|
||||
|
||||
Sidekiq::RetrySet.new.each do |retri|
|
||||
retri.delete if retri.klass == job_klass.to_s
|
||||
end
|
||||
|
||||
Sidekiq::ScheduledSet.new.each do |scheduled|
|
||||
scheduled.delete if scheduled.klass == job_klass.to_s
|
||||
end
|
||||
end
|
||||
# rubocop:enable Cop/SidekiqApiUsage
|
||||
|
||||
def sidekiq_queue_migrate(queue_from, to:)
|
||||
while sidekiq_queue_length(queue_from) > 0
|
||||
Sidekiq.redis do |conn|
|
||||
|
|
|
@ -10,6 +10,14 @@ module Gitlab
|
|||
|
||||
RECEIVED_HEADER_REGEX = /for\s+\<(.+)\>/.freeze
|
||||
|
||||
# Errors that are purely from users and not anything we can control
|
||||
USER_ERRORS = [
|
||||
Gitlab::Email::AutoGeneratedEmailError, Gitlab::Email::ProjectNotFound, Gitlab::Email::EmptyEmailError,
|
||||
Gitlab::Email::UserNotFoundError, Gitlab::Email::UserBlockedError, Gitlab::Email::UserNotAuthorizedError,
|
||||
Gitlab::Email::NoteableNotFoundError, Gitlab::Email::InvalidAttachment, Gitlab::Email::InvalidRecordError,
|
||||
Gitlab::Email::EmailTooLarge
|
||||
].freeze
|
||||
|
||||
def initialize(raw)
|
||||
@raw = raw
|
||||
end
|
||||
|
@ -24,6 +32,9 @@ module Gitlab
|
|||
handler.execute.tap do
|
||||
Gitlab::Metrics::BackgroundTransaction.current&.add_event(handler.metrics_event, handler.metrics_params)
|
||||
end
|
||||
rescue *USER_ERRORS => e
|
||||
# do not send a metric event since these are purely user errors that we can't control
|
||||
raise e
|
||||
rescue StandardError => e
|
||||
Gitlab::Metrics::BackgroundTransaction.current&.add_event('email_receiver_error', error: e.class.name)
|
||||
raise e
|
||||
|
|
|
@ -55,3 +55,5 @@
|
|||
- i_package_terraform_module_delete_package
|
||||
- i_package_terraform_module_pull_package
|
||||
- i_package_terraform_module_push_package
|
||||
- i_package_rpm_push_package
|
||||
- i_package_rpm_pull_package
|
||||
|
|
|
@ -79,3 +79,11 @@
|
|||
category: user_packages
|
||||
aggregation: weekly
|
||||
redis_slot: package
|
||||
- name: i_package_rpm_user
|
||||
category: user_packages
|
||||
aggregation: weekly
|
||||
redis_slot: package
|
||||
- name: i_package_rpm_deploy_token
|
||||
category: deploy_token_packages
|
||||
aggregation: weekly
|
||||
redis_slot: package
|
||||
|
|
|
@ -120,15 +120,47 @@ module UnnestedInFilters
|
|||
# "vulnerability_reads"."vulnerability_id" DESC
|
||||
# LIMIT 20
|
||||
#
|
||||
# If one of the columns being used for filtering or ordering is the primary key,
|
||||
# then the query will be further optimized to use an index-only scan for initial filtering
|
||||
# before selecting all columns using the primary key.
|
||||
#
|
||||
# Using the prior query as an example, where `vulnerability_id` is the primary key,
|
||||
# This will be rewritten to:
|
||||
#
|
||||
# SELECT
|
||||
# "vulnerability_reads".*
|
||||
# FROM
|
||||
# "vulnerability_reads"
|
||||
# WHERE
|
||||
# "vulnerability_reads"."vulnerability_id"
|
||||
# IN (
|
||||
# SELECT
|
||||
# "vulnerability_reads"."vulnerability_id"
|
||||
# FROM
|
||||
# unnest('{1, 4}'::smallint[]) AS "states" ("state"),
|
||||
# LATERAL (
|
||||
# SELECT
|
||||
# "vulnerability_reads"."vulnerability_id"
|
||||
# FROM
|
||||
# "vulnerability_reads"
|
||||
# WHERE
|
||||
# (vulnerability_reads."state" = "states"."state")
|
||||
# ORDER BY
|
||||
# "vulnerability_reads"."severity" DESC,
|
||||
# "vulnerability_reads"."vulnerability_id" DESC
|
||||
# LIMIT 20
|
||||
# ) AS vulnerability_reads
|
||||
# )
|
||||
# ORDER BY
|
||||
# "vulnerability_reads"."severity" DESC,
|
||||
# "vulnerability_reads"."vulnerability_id" DESC
|
||||
# LIMIT 20
|
||||
def rewrite
|
||||
log_rewrite
|
||||
|
||||
model.from(from)
|
||||
.limit(limit_value)
|
||||
.order(order_values)
|
||||
.includes(relation.includes_values)
|
||||
.preload(relation.preload_values)
|
||||
.eager_load(relation.eager_load_values)
|
||||
return filter_query unless primary_key_present?
|
||||
|
||||
index_only_filter_query
|
||||
end
|
||||
|
||||
def rewrite?
|
||||
|
@ -147,6 +179,23 @@ module UnnestedInFilters
|
|||
::Gitlab::AppLogger.info(message: 'Query is being rewritten by `UnnestedInFilters`', model: model.name)
|
||||
end
|
||||
|
||||
def filter_query
|
||||
model.from(from).then { add_relation_defaults(_1) }
|
||||
end
|
||||
|
||||
def index_only_filter_query
|
||||
model.where(model.primary_key => filter_query.select(model.primary_key))
|
||||
.then { add_relation_defaults(_1) }
|
||||
end
|
||||
|
||||
def add_relation_defaults(new_relation)
|
||||
new_relation.limit(limit_value)
|
||||
.order(order_values)
|
||||
.includes(relation.includes_values)
|
||||
.preload(relation.preload_values)
|
||||
.eager_load(relation.eager_load_values)
|
||||
end
|
||||
|
||||
def from
|
||||
[value_tables.map(&:to_sql) + [lateral]].join(', ')
|
||||
end
|
||||
|
@ -156,9 +205,13 @@ module UnnestedInFilters
|
|||
end
|
||||
|
||||
def join_relation
|
||||
value_tables.reduce(unscoped_relation) do |memo, tmp_table|
|
||||
join_relation = value_tables.reduce(unscoped_relation) do |memo, tmp_table|
|
||||
memo.where(tmp_table.as_predicate)
|
||||
end
|
||||
|
||||
join_relation = join_relation.select(combined_attributes) if primary_key_present?
|
||||
|
||||
join_relation
|
||||
end
|
||||
|
||||
def unscoped_relation
|
||||
|
@ -194,10 +247,18 @@ module UnnestedInFilters
|
|||
indices.any? do |index|
|
||||
(filter_attributes - Array(index.columns)).empty? && # all the filter attributes are indexed
|
||||
index.columns.last(order_attributes.length) == order_attributes && # index can be used in sorting
|
||||
(index.columns - (filter_attributes + order_attributes)).empty? # there is no other columns in the index
|
||||
(index.columns - combined_attributes).empty? # there is no other columns in the index
|
||||
end
|
||||
end
|
||||
|
||||
def primary_key_present?
|
||||
combined_attributes.include?(model.primary_key)
|
||||
end
|
||||
|
||||
def combined_attributes
|
||||
filter_attributes + order_attributes
|
||||
end
|
||||
|
||||
def filter_attributes
|
||||
@filter_attributes ||= where_values_hash.keys
|
||||
end
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
{
|
||||
"files": [
|
||||
"/usr/bin/hello.sh"
|
||||
"/usr/bin/test",
|
||||
"/usr/bin/test/hello.sh"
|
||||
],
|
||||
"changelogs": [
|
||||
{
|
||||
"changelogtext": "First build",
|
||||
"changelogtime": 1662552000
|
||||
},
|
||||
{
|
||||
"changelogtext": "Next build",
|
||||
"changelogtime": 1662552123
|
||||
},
|
||||
{
|
||||
"changelogtext": "Last build",
|
||||
"changelogtime": 1662552321
|
||||
}
|
||||
],
|
||||
"requirements": [
|
||||
|
@ -43,5 +52,7 @@
|
|||
"group": "Unspecified",
|
||||
"buildhost": "localhost",
|
||||
"packager": null,
|
||||
"vendor": null
|
||||
"vendor": null,
|
||||
"pkgid": "qwe123wer234ert345",
|
||||
"epoch": "1"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
import addBlameLink from '~/blob/blob_blame_link';
|
||||
import { addBlameLink } from '~/blob/blob_blame_link';
|
||||
|
||||
describe('Blob links', () => {
|
||||
const mouseoverEvent = new MouseEvent('mouseover', {
|
||||
|
@ -10,9 +10,10 @@ describe('Blob links', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
setHTMLFixture(`
|
||||
<div id="blob-content-holder">
|
||||
<div id="blob-content-holder" class="js-per-page" data-blame-per-page="1000">
|
||||
<div class="line-numbers" data-blame-path="/blamePath">
|
||||
<a id="L5" href="#L5" data-line-number="5" class="file-line-num js-line-links">5</a>
|
||||
<a id="L1005" href="#L1005" data-line-number="1005" class="file-line-num js-line-links">1005</a>
|
||||
</div>
|
||||
<pre id="LC5">Line 5 content</pre>
|
||||
</div>
|
||||
|
@ -44,4 +45,11 @@ describe('Blob links', () => {
|
|||
expect(lineLink).not.toBeNull();
|
||||
expect(lineLink.getAttribute('href')).toBe('#L5');
|
||||
});
|
||||
|
||||
it('adds page parameter when needed', () => {
|
||||
document.querySelectorAll('.file-line-num')[1].dispatchEvent(mouseoverEvent);
|
||||
const blameLink = document.querySelectorAll('.file-line-blame')[1];
|
||||
expect(blameLink).not.toBeNull();
|
||||
expect(blameLink.getAttribute('href')).toBe('/blamePath?page=2#L1005');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,4 +41,32 @@ describe('Blob utilities', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
describe('getPageParamValue', () => {
|
||||
it('returns empty string if no perPage parameter is provided', () => {
|
||||
const pageParamValue = utils.getPageParamValue(5);
|
||||
expect(pageParamValue).toEqual('');
|
||||
});
|
||||
it('returns empty string if page is equal 1', () => {
|
||||
const pageParamValue = utils.getPageParamValue(1000, 1000);
|
||||
expect(pageParamValue).toEqual('');
|
||||
});
|
||||
it('returns correct page parameter value', () => {
|
||||
const pageParamValue = utils.getPageParamValue(1001, 1000);
|
||||
expect(pageParamValue).toEqual(2);
|
||||
});
|
||||
it('accepts strings as a parameter and returns correct result', () => {
|
||||
const pageParamValue = utils.getPageParamValue('1001', '1000');
|
||||
expect(pageParamValue).toEqual(2);
|
||||
});
|
||||
});
|
||||
describe('getPageSearchString', () => {
|
||||
it('returns empty search string if page parameter is empty value', () => {
|
||||
const path = utils.getPageSearchString('/blamePath', '');
|
||||
expect(path).toEqual('');
|
||||
});
|
||||
it('returns correct search string if value is provided', () => {
|
||||
const searchString = utils.getPageSearchString('/blamePath', 3);
|
||||
expect(searchString).toEqual('?page=3');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -193,16 +193,18 @@ describe('deprecatedJQueryDropdown', () => {
|
|||
});
|
||||
|
||||
it('should not focus search input while remote task is not complete', () => {
|
||||
expect($(document.activeElement)).not.toEqual($(SEARCH_INPUT_SELECTOR));
|
||||
expect(document.activeElement).toBeDefined();
|
||||
expect(document.activeElement).not.toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
|
||||
remoteCallback();
|
||||
|
||||
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
|
||||
expect(document.activeElement).toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
|
||||
});
|
||||
|
||||
it('should focus search input after remote task is complete', () => {
|
||||
remoteCallback();
|
||||
|
||||
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
|
||||
expect(document.activeElement).toBeDefined();
|
||||
expect(document.activeElement).toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
|
||||
});
|
||||
|
||||
it('should focus on input when opening for the second time after transition', () => {
|
||||
|
@ -215,7 +217,8 @@ describe('deprecatedJQueryDropdown', () => {
|
|||
test.dropdownButtonElement.click();
|
||||
test.dropdownContainerElement.trigger('transitionend');
|
||||
|
||||
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
|
||||
expect(document.activeElement).toBeDefined();
|
||||
expect(document.activeElement).toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -225,7 +228,8 @@ describe('deprecatedJQueryDropdown', () => {
|
|||
test.dropdownButtonElement.click();
|
||||
test.dropdownContainerElement.trigger('transitionend');
|
||||
|
||||
expect($(document.activeElement)).toEqual($(SEARCH_INPUT_SELECTOR));
|
||||
expect(document.activeElement).toBeDefined();
|
||||
expect(document.activeElement).toEqual(document.querySelector(SEARCH_INPUT_SELECTOR));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@ import { GlIntersectionObserver } from '@gitlab/ui';
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
|
||||
import ChunkLine from '~/vue_shared/components/source_viewer/components/chunk_line.vue';
|
||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||
|
||||
jest.mock('~/lib/utils/common_utils');
|
||||
|
||||
const DEFAULT_PROPS = {
|
||||
chunkIndex: 2,
|
||||
|
@ -13,11 +16,17 @@ const DEFAULT_PROPS = {
|
|||
blamePath: 'blame/file.js',
|
||||
};
|
||||
|
||||
const hash = '#L142';
|
||||
|
||||
describe('Chunk component', () => {
|
||||
let wrapper;
|
||||
let idleCallbackSpy;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMountExtended(Chunk, { propsData: { ...DEFAULT_PROPS, ...props } });
|
||||
wrapper = shallowMountExtended(Chunk, {
|
||||
mocks: { $route: { hash } },
|
||||
propsData: { ...DEFAULT_PROPS, ...props },
|
||||
});
|
||||
};
|
||||
|
||||
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
|
||||
|
@ -26,6 +35,7 @@ describe('Chunk component', () => {
|
|||
const findContent = () => wrapper.findByTestId('content');
|
||||
|
||||
beforeEach(() => {
|
||||
idleCallbackSpy = jest.spyOn(window, 'requestIdleCallback').mockImplementation((fn) => fn());
|
||||
createComponent();
|
||||
});
|
||||
|
||||
|
@ -55,6 +65,14 @@ describe('Chunk component', () => {
|
|||
expect(findChunkLines().length).toBe(0);
|
||||
});
|
||||
|
||||
it('does not render simplified line numbers and content if browser is not in idle state', () => {
|
||||
idleCallbackSpy.mockRestore();
|
||||
createComponent();
|
||||
|
||||
expect(findLineNumbers()).toHaveLength(0);
|
||||
expect(findContent().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders simplified line numbers and content if isHighlighted is false', () => {
|
||||
expect(findLineNumbers().length).toBe(DEFAULT_PROPS.totalLines);
|
||||
|
||||
|
@ -76,5 +94,14 @@ describe('Chunk component', () => {
|
|||
blamePath: DEFAULT_PROPS.blamePath,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not scroll to route hash if last chunk is not loaded', () => {
|
||||
expect(scrollToElement).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('scrolls to route hash if last chunk is loaded', () => {
|
||||
createComponent({ totalChunks: DEFAULT_PROPS.chunkIndex + 1 });
|
||||
expect(scrollToElement).toHaveBeenCalledWith(hash, { behavior: 'auto' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
EVENT_LABEL_VIEWER,
|
||||
EVENT_LABEL_FALLBACK,
|
||||
ROUGE_TO_HLJS_LANGUAGE_MAP,
|
||||
LINES_PER_CHUNK,
|
||||
} from '~/vue_shared/components/source_viewer/constants';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import LineHighlighter from '~/blob/line_highlighter';
|
||||
|
@ -133,45 +134,27 @@ describe('Source Viewer component', () => {
|
|||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
it('renders the first chunk', async () => {
|
||||
const firstChunk = findChunks().at(0);
|
||||
it.each`
|
||||
chunkIndex | chunkContent | totalChunks
|
||||
${0} | ${chunk1} | ${0}
|
||||
${1} | ${chunk2} | ${3}
|
||||
${2} | ${chunk3Result} | ${3}
|
||||
`('renders chunk $chunkIndex', ({ chunkIndex, chunkContent, totalChunks }) => {
|
||||
const chunk = findChunks().at(chunkIndex);
|
||||
|
||||
expect(firstChunk.props('content')).toContain(chunk1);
|
||||
expect(chunk.props('content')).toContain(chunkContent.trim());
|
||||
|
||||
expect(firstChunk.props()).toMatchObject({
|
||||
totalLines: 70,
|
||||
startingFrom: 0,
|
||||
expect(chunk.props()).toMatchObject({
|
||||
totalLines: LINES_PER_CHUNK,
|
||||
startingFrom: LINES_PER_CHUNK * chunkIndex,
|
||||
totalChunks,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the second chunk', async () => {
|
||||
const secondChunk = findChunks().at(1);
|
||||
|
||||
expect(secondChunk.props('content')).toContain(chunk2.trim());
|
||||
|
||||
expect(secondChunk.props()).toMatchObject({
|
||||
totalLines: 70,
|
||||
startingFrom: 70,
|
||||
});
|
||||
it('emits showBlobInteractionZones on the eventHub when chunk appears', () => {
|
||||
findChunks().at(0).vm.$emit('appear');
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('showBlobInteractionZones', path);
|
||||
});
|
||||
|
||||
it('renders the third chunk', async () => {
|
||||
const thirdChunk = findChunks().at(2);
|
||||
|
||||
expect(thirdChunk.props('content')).toContain(chunk3Result.trim());
|
||||
|
||||
expect(chunk3Result).toEqual(chunk3.replace(/\r?\n/g, '\n'));
|
||||
|
||||
expect(thirdChunk.props()).toMatchObject({
|
||||
totalLines: 70,
|
||||
startingFrom: 140,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('emits showBlobInteractionZones on the eventHub when chunk appears', () => {
|
||||
findChunks().at(0).vm.$emit('appear');
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith('showBlobInteractionZones', path);
|
||||
});
|
||||
|
||||
describe('LineHighlighter', () => {
|
||||
|
|
|
@ -1924,8 +1924,116 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
end
|
||||
end
|
||||
|
||||
let(:same_queue_different_worker) do
|
||||
Class.new do
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options queue: 'test'
|
||||
|
||||
def self.name
|
||||
'SameQueueDifferentWorkerClass'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:unrelated_worker) do
|
||||
Class.new do
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options queue: 'unrelated'
|
||||
|
||||
def self.name
|
||||
'UnrelatedWorkerClass'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
stub_const(worker.name, worker)
|
||||
stub_const(unrelated_worker.name, unrelated_worker)
|
||||
stub_const(same_queue_different_worker.name, same_queue_different_worker)
|
||||
end
|
||||
|
||||
describe '#sidekiq_remove_jobs', :clean_gitlab_redis_queues do
|
||||
def clear_queues
|
||||
Sidekiq::Queue.new('test').clear
|
||||
Sidekiq::Queue.new('unrelated').clear
|
||||
Sidekiq::RetrySet.new.clear
|
||||
Sidekiq::ScheduledSet.new.clear
|
||||
end
|
||||
|
||||
around do |example|
|
||||
clear_queues
|
||||
Sidekiq::Testing.disable!(&example)
|
||||
clear_queues
|
||||
end
|
||||
|
||||
it "removes all related job instances from the job class's queue" do
|
||||
worker.perform_async
|
||||
same_queue_different_worker.perform_async
|
||||
unrelated_worker.perform_async
|
||||
|
||||
queue_we_care_about = Sidekiq::Queue.new(worker.queue)
|
||||
unrelated_queue = Sidekiq::Queue.new(unrelated_worker.queue)
|
||||
|
||||
expect(queue_we_care_about.size).to eq(2)
|
||||
expect(unrelated_queue.size).to eq(1)
|
||||
|
||||
model.sidekiq_remove_jobs(job_klass: worker)
|
||||
|
||||
expect(queue_we_care_about.size).to eq(1)
|
||||
expect(queue_we_care_about.map(&:klass)).not_to include(worker.name)
|
||||
expect(queue_we_care_about.map(&:klass)).to include(
|
||||
same_queue_different_worker.name
|
||||
)
|
||||
expect(unrelated_queue.size).to eq(1)
|
||||
end
|
||||
|
||||
context 'when job instances are in the scheduled set' do
|
||||
it 'removes all related job instances from the scheduled set' do
|
||||
worker.perform_in(1.hour)
|
||||
unrelated_worker.perform_in(1.hour)
|
||||
|
||||
scheduled = Sidekiq::ScheduledSet.new
|
||||
|
||||
expect(scheduled.size).to eq(2)
|
||||
expect(scheduled.map(&:klass)).to include(
|
||||
worker.name,
|
||||
unrelated_worker.name
|
||||
)
|
||||
|
||||
model.sidekiq_remove_jobs(job_klass: worker)
|
||||
|
||||
expect(scheduled.size).to eq(1)
|
||||
expect(scheduled.map(&:klass)).not_to include(worker.name)
|
||||
expect(scheduled.map(&:klass)).to include(unrelated_worker.name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when job instances are in the retry set' do
|
||||
include_context 'when handling retried jobs'
|
||||
|
||||
it 'removes all related job instances from the retry set' do
|
||||
retry_in(worker, 1.hour)
|
||||
retry_in(worker, 2.hours)
|
||||
retry_in(worker, 3.hours)
|
||||
retry_in(unrelated_worker, 4.hours)
|
||||
|
||||
retries = Sidekiq::RetrySet.new
|
||||
|
||||
expect(retries.size).to eq(4)
|
||||
expect(retries.map(&:klass)).to include(
|
||||
worker.name,
|
||||
unrelated_worker.name
|
||||
)
|
||||
|
||||
model.sidekiq_remove_jobs(job_klass: worker)
|
||||
|
||||
expect(retries.size).to eq(1)
|
||||
expect(retries.map(&:klass)).not_to include(worker.name)
|
||||
expect(retries.map(&:klass)).to include(unrelated_worker.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sidekiq_queue_length' do
|
||||
|
@ -1949,7 +2057,7 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#migrate_sidekiq_queue' do
|
||||
describe '#sidekiq_queue_migrate' do
|
||||
it 'migrates jobs from one sidekiq queue to another' do
|
||||
Sidekiq::Testing.disable! do
|
||||
worker.perform_async('Something', [1])
|
||||
|
|
|
@ -5,11 +5,10 @@ require 'spec_helper'
|
|||
RSpec.describe Gitlab::Email::Receiver do
|
||||
include_context :email_shared_context
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:metric_transaction) { instance_double(Gitlab::Metrics::WebTransaction) }
|
||||
|
||||
shared_examples 'successful receive' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:handler) { double(:handler, project: project, execute: true, metrics_event: nil, metrics_params: nil) }
|
||||
let(:client_id) { 'email/jake@example.com' }
|
||||
|
||||
|
@ -39,7 +38,7 @@ RSpec.describe Gitlab::Email::Receiver do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'failed receive' do
|
||||
shared_examples 'failed receive with event' do
|
||||
it 'adds metric event' do
|
||||
expect(::Gitlab::Metrics::BackgroundTransaction).to receive(:current).and_return(metric_transaction)
|
||||
expect(metric_transaction).to receive(:add_event).with('email_receiver_error', { error: expected_error.name })
|
||||
|
@ -48,6 +47,14 @@ RSpec.describe Gitlab::Email::Receiver do
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'failed receive without event' do
|
||||
it 'adds metric event' do
|
||||
expect(::Gitlab::Metrics::BackgroundTransaction).not_to receive(:current)
|
||||
|
||||
expect { receiver.execute }.to raise_error(expected_error)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the email contains a valid email address in a header' do
|
||||
before do
|
||||
stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.example.com")
|
||||
|
@ -100,21 +107,21 @@ RSpec.describe Gitlab::Email::Receiver do
|
|||
let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, '!!!') }
|
||||
let(:expected_error) { Gitlab::Email::UnknownIncomingEmail }
|
||||
|
||||
it_behaves_like 'failed receive'
|
||||
it_behaves_like 'failed receive with event'
|
||||
end
|
||||
|
||||
context 'when the email is blank' do
|
||||
let(:email_raw) { '' }
|
||||
let(:expected_error) { Gitlab::Email::EmptyEmailError }
|
||||
|
||||
it_behaves_like 'failed receive'
|
||||
it_behaves_like 'failed receive without event'
|
||||
end
|
||||
|
||||
context 'when the email was auto generated with Auto-Submitted header' do
|
||||
let(:email_raw) { fixture_file('emails/auto_submitted.eml') }
|
||||
let(:expected_error) { Gitlab::Email::AutoGeneratedEmailError }
|
||||
|
||||
it_behaves_like 'failed receive'
|
||||
it_behaves_like 'failed receive without event'
|
||||
end
|
||||
|
||||
context "when the email's To field is blank" do
|
||||
|
@ -164,7 +171,48 @@ RSpec.describe Gitlab::Email::Receiver do
|
|||
let(:email_raw) { fixture_file('emails/auto_reply.eml') }
|
||||
let(:expected_error) { Gitlab::Email::AutoGeneratedEmailError }
|
||||
|
||||
it_behaves_like 'failed receive'
|
||||
it_behaves_like 'failed receive without event'
|
||||
end
|
||||
|
||||
describe 'event raising via errors' do
|
||||
let(:handler) { double(:handler, project: project, execute: true, metrics_event: nil, metrics_params: nil) }
|
||||
let(:email_raw) { "arbitrary text. could be anything really. we're going to raise an error anyway." }
|
||||
|
||||
before do
|
||||
allow(receiver).to receive(:handler).and_return(handler)
|
||||
allow(handler).to receive(:execute).and_raise(expected_error)
|
||||
end
|
||||
|
||||
describe 'handling errors which do not raise events' do
|
||||
where(:expected_error) do
|
||||
[
|
||||
Gitlab::Email::AutoGeneratedEmailError,
|
||||
Gitlab::Email::ProjectNotFound,
|
||||
Gitlab::Email::EmptyEmailError,
|
||||
Gitlab::Email::UserNotFoundError,
|
||||
Gitlab::Email::UserBlockedError,
|
||||
Gitlab::Email::UserNotAuthorizedError,
|
||||
Gitlab::Email::NoteableNotFoundError,
|
||||
Gitlab::Email::InvalidAttachment,
|
||||
Gitlab::Email::InvalidRecordError,
|
||||
Gitlab::Email::EmailTooLarge
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'failed receive without event'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'handling errors which do raise events' do
|
||||
where(:expected_error) do
|
||||
[Gitlab::Email::EmailUnparsableError, Gitlab::Email::UnknownIncomingEmail, ArgumentError, StandardError]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it_behaves_like 'failed receive with event'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'requires all handlers to have a unique metric_event' do
|
||||
|
|
|
@ -208,25 +208,6 @@ RSpec.describe Gitlab::SidekiqMigrateJobs, :clean_gitlab_redis_queues do
|
|||
end
|
||||
|
||||
context 'retried jobs' do
|
||||
let(:set_name) { 'retry' }
|
||||
# Account for Sidekiq retry jitter
|
||||
# https://github.com/mperham/sidekiq/blob/3575ccb44c688dd08bfbfd937696260b12c622fb/lib/sidekiq/job_retry.rb#L217
|
||||
let(:schedule_jitter) { 10 }
|
||||
|
||||
# Try to mimic as closely as possible what Sidekiq will actually
|
||||
# do to retry a job.
|
||||
def retry_in(klass, time, args)
|
||||
message = { 'class' => klass.name, 'args' => [args], 'retry' => true }.to_json
|
||||
|
||||
allow(klass).to receive(:sidekiq_retry_in_block).and_return(proc { time })
|
||||
|
||||
begin
|
||||
Sidekiq::JobRetry.new.local(klass, message, klass.queue) { raise 'boom' }
|
||||
rescue Sidekiq::JobRetry::Skip
|
||||
# Sidekiq scheduled the retry
|
||||
end
|
||||
end
|
||||
|
||||
def create_jobs(include_post_receive: true)
|
||||
retry_in(AuthorizedProjectsWorker, 1.hour, 0)
|
||||
retry_in(AuthorizedProjectsWorker, 2.hours, 1)
|
||||
|
@ -234,6 +215,7 @@ RSpec.describe Gitlab::SidekiqMigrateJobs, :clean_gitlab_redis_queues do
|
|||
retry_in(AuthorizedProjectsWorker, 4.hours, 3)
|
||||
end
|
||||
|
||||
include_context 'when handling retried jobs'
|
||||
it_behaves_like 'processing a set'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -170,6 +170,51 @@ RSpec.describe UnnestedInFilters::Rewriter do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the combined attributes include the primary key' do
|
||||
let(:relation) { User.where(user_type: %i(support_bot alert_bot)).order(id: :desc).limit(2) }
|
||||
|
||||
let(:expected_query) do
|
||||
<<~SQL
|
||||
SELECT
|
||||
"users".*
|
||||
FROM
|
||||
"users"
|
||||
WHERE
|
||||
"users"."id" IN (
|
||||
SELECT
|
||||
"users"."id"
|
||||
FROM
|
||||
unnest('{1,2}' :: smallint []) AS "user_types"("user_type"),
|
||||
LATERAL (
|
||||
SELECT
|
||||
"users"."user_type",
|
||||
"users"."id"
|
||||
FROM
|
||||
"users"
|
||||
WHERE
|
||||
(users."user_type" = "user_types"."user_type")
|
||||
ORDER BY
|
||||
"users"."id" DESC
|
||||
LIMIT
|
||||
2
|
||||
) AS users
|
||||
ORDER BY
|
||||
"users"."id" DESC
|
||||
LIMIT
|
||||
2
|
||||
)
|
||||
ORDER BY
|
||||
"users"."id" DESC
|
||||
LIMIT
|
||||
2
|
||||
SQL
|
||||
end
|
||||
|
||||
it 'changes the query' do
|
||||
expect(issued_query.gsub(/\s/, '')).to start_with(expected_query.gsub(/\s/, ''))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'logging' do
|
||||
subject(:load_reload) { rewriter.rewrite }
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ RSpec.describe API::Integrations::JiraConnect::Subscriptions do
|
|||
|
||||
context 'with feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(jira_connect_oauth_self_managed: false)
|
||||
stub_feature_flags(jira_connect_oauth: false)
|
||||
end
|
||||
|
||||
let(:jwt) { '123' }
|
||||
|
|
|
@ -9,7 +9,8 @@ RSpec.describe API::RpmProjectPackages do
|
|||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be_with_reload(:project) { create(:project, :public) }
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be_with_reload(:project) { create(:project, :public, group: group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
|
||||
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
|
||||
|
@ -133,16 +134,19 @@ RSpec.describe API::RpmProjectPackages do
|
|||
end
|
||||
|
||||
describe 'GET /api/v4/projects/:id/packages/rpm/:package_file_id/:filename' do
|
||||
let(:snowplow_gitlab_standard_context) { { project: project, namespace: group } }
|
||||
let(:url) { "/projects/#{project.id}/packages/rpm/#{package_file_id}/#{package_name}" }
|
||||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
|
||||
it_behaves_like 'a job token for RPM requests'
|
||||
it_behaves_like 'a deploy token for RPM requests'
|
||||
it_behaves_like 'a user token for RPM requests'
|
||||
end
|
||||
|
||||
describe 'POST /api/v4/projects/:project_id/packages/rpm' do
|
||||
let(:snowplow_gitlab_standard_context) { { project: project, namespace: group, user: user } }
|
||||
let(:url) { "/projects/#{project.id}/packages/rpm" }
|
||||
let(:file_upload) { fixture_file_upload('spec/fixtures/packages/rpm/hello-0.0.1-1.fc29.x86_64.rpm') }
|
||||
|
||||
|
@ -150,25 +154,25 @@ RSpec.describe API::RpmProjectPackages do
|
|||
|
||||
context 'with user token' do
|
||||
context 'with valid project' do
|
||||
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'process rpm packages upload/download' | :not_found
|
||||
'PUBLIC' | :guest | true | true | 'rejects rpm packages access' | :forbidden
|
||||
'PUBLIC' | :developer | true | false | 'rejects rpm packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'rejects rpm packages access' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'rejects rpm packages access' | :not_found
|
||||
'PUBLIC' | :guest | false | true | 'rejects rpm packages access' | :not_found
|
||||
'PUBLIC' | :developer | false | false | 'rejects rpm packages access' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'rejects rpm packages access' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'rejects rpm packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | true | true | 'process rpm packages upload/download' | :not_found
|
||||
'PRIVATE' | :guest | true | true | 'rejects rpm packages access' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'rejects rpm packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'rejects rpm packages access' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'rejects rpm packages access' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'rejects rpm packages access' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'rejects rpm packages access' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'rejects rpm packages access' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects rpm packages access' | :unauthorized
|
||||
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status, :tracked) do
|
||||
'PUBLIC' | :developer | true | true | 'process rpm packages upload/download' | :not_found | true
|
||||
'PUBLIC' | :guest | true | true | 'rejects rpm packages access' | :forbidden | false
|
||||
'PUBLIC' | :developer | true | false | 'rejects rpm packages access' | :unauthorized | false
|
||||
'PUBLIC' | :guest | true | false | 'rejects rpm packages access' | :unauthorized | false
|
||||
'PUBLIC' | :developer | false | true | 'rejects rpm packages access' | :not_found | false
|
||||
'PUBLIC' | :guest | false | true | 'rejects rpm packages access' | :not_found | false
|
||||
'PUBLIC' | :developer | false | false | 'rejects rpm packages access' | :unauthorized | false
|
||||
'PUBLIC' | :guest | false | false | 'rejects rpm packages access' | :unauthorized | false
|
||||
'PUBLIC' | :anonymous | false | true | 'rejects rpm packages access' | :unauthorized | false
|
||||
'PRIVATE' | :developer | true | true | 'process rpm packages upload/download' | :not_found | true
|
||||
'PRIVATE' | :guest | true | true | 'rejects rpm packages access' | :forbidden | false
|
||||
'PRIVATE' | :developer | true | false | 'rejects rpm packages access' | :unauthorized | false
|
||||
'PRIVATE' | :guest | true | false | 'rejects rpm packages access' | :unauthorized | false
|
||||
'PRIVATE' | :developer | false | true | 'rejects rpm packages access' | :not_found | false
|
||||
'PRIVATE' | :guest | false | true | 'rejects rpm packages access' | :not_found | false
|
||||
'PRIVATE' | :developer | false | false | 'rejects rpm packages access' | :unauthorized | false
|
||||
'PRIVATE' | :guest | false | false | 'rejects rpm packages access' | :unauthorized | false
|
||||
'PRIVATE' | :anonymous | false | true | 'rejects rpm packages access' | :unauthorized | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
@ -180,6 +184,8 @@ RSpec.describe API::RpmProjectPackages do
|
|||
project.send("add_#{user_role}", user) if member && user_role != :anonymous
|
||||
end
|
||||
|
||||
tracking_example = params[:tracked] ? 'a package tracking event' : 'not a package tracking event'
|
||||
it_behaves_like tracking_example, described_class.name, 'push_package'
|
||||
it_behaves_like params[:shared_examples_name], params[:expected_status]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -56,17 +56,6 @@ RSpec.describe Branches::CreateService, :use_clean_rails_redis_caching do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when an ambiguous branch name is provided' do
|
||||
let(:branches) { { 'ambiguous/test' => 'master', 'ambiguous' => 'master' } }
|
||||
|
||||
it 'returns an error that branch could not be created' do
|
||||
err_msg = 'Failed to create branch \'ambiguous\': 13:reference is ambiguous.'
|
||||
|
||||
expect(subject[:status]).to eq(:error)
|
||||
expect(subject[:message]).to match_array([err_msg])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when PreReceiveError exception' do
|
||||
let(:branches) { { 'error' => 'master' } }
|
||||
|
||||
|
@ -184,18 +173,6 @@ RSpec.describe Branches::CreateService, :use_clean_rails_redis_caching do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when an ambiguous branch name is provided' do
|
||||
it 'returns an error that branch could not be created' do
|
||||
err_msg = 'Failed to create branch \'feature\': 13:reference is ambiguous.'
|
||||
|
||||
service.execute('feature/widget', 'master')
|
||||
result = service.execute('feature', 'master')
|
||||
|
||||
expect(result[:status]).to eq(:error)
|
||||
expect(result[:message]).to eq(err_msg)
|
||||
end
|
||||
end
|
||||
|
||||
it 'logs and returns an error if there is a PreReceiveError exception' do
|
||||
error_message = 'pre receive error'
|
||||
raw_message = "GitLab: #{error_message}"
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Rpm::RepositoryMetadata::BaseBuilder do
|
||||
describe '#execute' do
|
||||
subject { described_class.new(xml: xml, data: data).execute }
|
||||
|
||||
let(:xml) { nil }
|
||||
let(:data) { {} }
|
||||
|
||||
before do
|
||||
stub_const("#{described_class}::ROOT_TAG", 'test')
|
||||
stub_const("#{described_class}::ROOT_ATTRIBUTES", { foo1: 'bar1', foo2: 'bar2' })
|
||||
end
|
||||
|
||||
it 'generate valid xml' do
|
||||
result = Nokogiri::XML::Document.parse(subject)
|
||||
|
||||
expect(result.children.count).to eq(1)
|
||||
expect(result.children.first.attributes.count).to eq(2)
|
||||
expect(result.children.first.attributes['foo1'].value).to eq('bar1')
|
||||
expect(result.children.first.attributes['foo2'].value).to eq('bar2')
|
||||
end
|
||||
|
||||
context 'when call with parameters' do
|
||||
let(:xml) { 'test' }
|
||||
|
||||
it 'raise NotImplementedError' do
|
||||
expect { subject }.to raise_error NotImplementedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildFilelistXml do
|
||||
describe '#execute' do
|
||||
subject { described_class.new.execute }
|
||||
|
||||
context "when generate empty xml" do
|
||||
let(:expected_xml) do
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<filelists xmlns="http://linux.duke.edu/metadata/filelists" packages="0"/>
|
||||
XML
|
||||
end
|
||||
|
||||
it 'generate expected xml' do
|
||||
expect(subject).to eq(expected_xml)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildOtherXmlService do
|
||||
describe '#execute' do
|
||||
subject { described_class.new(data).execute }
|
||||
|
||||
context 'when updating existing xml' do
|
||||
include_context 'with rpm package data'
|
||||
|
||||
let(:data) { xml_update_params }
|
||||
let(:changelog_xpath) { "//package/changelog" }
|
||||
|
||||
it 'adds all changelog nodes' do
|
||||
result = subject
|
||||
|
||||
expect(result.xpath(changelog_xpath).count).to eq(data[:changelogs].count)
|
||||
end
|
||||
|
||||
it 'set required date attribute' do
|
||||
result = subject
|
||||
|
||||
data[:changelogs].each do |changelog|
|
||||
expect(result.at("#{changelog_xpath}[@date=\"#{changelog[:changelogtime]}\"]")).not_to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildOtherXml do
|
||||
describe '#execute' do
|
||||
subject { described_class.new.execute }
|
||||
|
||||
context "when generate empty xml" do
|
||||
let(:expected_xml) do
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<otherdata xmlns="http://linux.duke.edu/metadata/other" packages="0"/>
|
||||
XML
|
||||
end
|
||||
|
||||
it 'generate expected xml' do
|
||||
expect(subject).to eq(expected_xml)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,32 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildPrimaryXml do
|
||||
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildPrimaryXmlService do
|
||||
describe '#execute' do
|
||||
subject { described_class.new(xml: xml, data: data).execute }
|
||||
|
||||
let(:empty_xml) do
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata xmlns="http://linux.duke.edu/metadata/common" xmlns:rpm="http://linux.duke.edu/metadata/rpm" packages="0"/>
|
||||
XML
|
||||
end
|
||||
|
||||
it_behaves_like 'handling rpm xml file'
|
||||
subject { described_class.new(data).execute }
|
||||
|
||||
context 'when updating existing xml' do
|
||||
include_context 'with rpm package data'
|
||||
|
||||
let(:xml) { empty_xml }
|
||||
let(:data) { xml_update_params }
|
||||
let(:required_text_only_attributes) { %i[description summary arch name] }
|
||||
|
||||
it 'adds node with required_text_only_attributes' do
|
||||
result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
|
||||
result = subject
|
||||
|
||||
required_text_only_attributes.each do |attribute|
|
||||
expect(
|
||||
result.at("//#{described_class::ROOT_TAG}/package/#{attribute}").text
|
||||
result.at("//package/#{attribute}").text
|
||||
).to eq(data[attribute])
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildRepomdXml do
|
||||
RSpec.describe Packages::Rpm::RepositoryMetadata::BuildRepomdXmlService do
|
||||
describe '#execute' do
|
||||
subject { described_class.new(data).execute }
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Rpm::RepositoryMetadata::UpdateXmlService do
|
||||
describe '#execute' do
|
||||
subject { described_class.new(filename: filename, xml: xml, data: data).execute }
|
||||
|
||||
let(:xml) { nil }
|
||||
let(:data) { nil }
|
||||
|
||||
shared_examples 'handling not implemented xml filename' do
|
||||
let(:filename) { :not_implemented_yet }
|
||||
let(:empty_xml) { '' }
|
||||
|
||||
it 'raise error' do
|
||||
expect { subject }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
shared_context 'with primary xml file data' do
|
||||
let(:filename) { :primary }
|
||||
let(:empty_xml) do
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata xmlns="http://linux.duke.edu/metadata/common" xmlns:rpm="http://linux.duke.edu/metadata/rpm" packages="0"/>
|
||||
XML
|
||||
end
|
||||
end
|
||||
|
||||
shared_context 'with other xml file data' do
|
||||
let(:filename) { :other }
|
||||
let(:empty_xml) do
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<otherdata xmlns="http://linux.duke.edu/metadata/other" packages="0"/>
|
||||
XML
|
||||
end
|
||||
end
|
||||
|
||||
context 'when building empty xml' do
|
||||
shared_examples 'generating empty xml' do
|
||||
it 'generate expected xml' do
|
||||
expect(subject).to eq(empty_xml)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'handling not implemented xml filename'
|
||||
|
||||
context "for 'primary' xml file" do
|
||||
include_context 'with primary xml file data'
|
||||
|
||||
it_behaves_like 'generating empty xml'
|
||||
end
|
||||
|
||||
context "for 'other' xml file" do
|
||||
include_context 'with other xml file data'
|
||||
|
||||
it_behaves_like 'generating empty xml'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating xml file' do
|
||||
include_context 'with rpm package data'
|
||||
|
||||
let(:xml) { empty_xml }
|
||||
let(:data) { xml_update_params }
|
||||
let(:builder_class) { described_class::BUILDERS[filename] }
|
||||
|
||||
shared_examples 'updating rpm xml file' do
|
||||
context 'when updating existing xml' do
|
||||
shared_examples 'changing root tag attribute' do
|
||||
it "increment previous 'packages' value by 1" do
|
||||
previous_value = Nokogiri::XML(xml).at(builder_class::ROOT_TAG).attributes["packages"].value.to_i
|
||||
new_value = Nokogiri::XML(subject).at(builder_class::ROOT_TAG).attributes["packages"].value.to_i
|
||||
|
||||
expect(previous_value + 1).to eq(new_value)
|
||||
end
|
||||
end
|
||||
|
||||
it 'generate valid xml add expected xml node to existing xml' do
|
||||
# Have one root attribute
|
||||
result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
|
||||
expect(result.children.count).to eq(1)
|
||||
|
||||
# Root node has 1 child with generated node
|
||||
expect(result.xpath("//#{builder_class::ROOT_TAG}/package").count).to eq(1)
|
||||
end
|
||||
|
||||
context 'when empty xml' do
|
||||
it_behaves_like 'changing root tag attribute'
|
||||
end
|
||||
|
||||
context 'when xml has children' do
|
||||
context "when node with given 'pkgid' does not exist yet" do
|
||||
let(:uniq_node_data) do
|
||||
xml_update_params.tap do |data|
|
||||
data[:pkgid] = SecureRandom.uuid
|
||||
end
|
||||
end
|
||||
|
||||
let(:xml) { build_xml_from(uniq_node_data) }
|
||||
|
||||
it 'has children nodes' do
|
||||
existing_xml = Nokogiri::XML::Document.parse(xml).remove_namespaces!
|
||||
expect(existing_xml.xpath('//package').count).to eq(1)
|
||||
end
|
||||
|
||||
it_behaves_like 'changing root tag attribute'
|
||||
end
|
||||
|
||||
context "when node with given 'pkgid' already exist" do
|
||||
let(:existing_node_data) do
|
||||
existing_data = data.dup
|
||||
existing_data[:name] = FFaker::Lorem.word
|
||||
existing_data
|
||||
end
|
||||
|
||||
let(:xml) { build_xml_from(existing_node_data) }
|
||||
|
||||
it 'has children nodes' do
|
||||
existing_xml = Nokogiri::XML::Document.parse(xml).remove_namespaces!
|
||||
expect(existing_xml.xpath('//package').count).to eq(1)
|
||||
end
|
||||
|
||||
it 'replace existing node with new data' do
|
||||
existing_xml = Nokogiri::XML::Document.parse(xml).remove_namespaces!
|
||||
result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
|
||||
expect(result.xpath('//package').count).to eq(1)
|
||||
expect(result.xpath('//package').first.to_xml).not_to eq(existing_xml.xpath('//package').first.to_xml)
|
||||
end
|
||||
end
|
||||
|
||||
def build_xml_from(data)
|
||||
described_class.new(filename: filename, xml: empty_xml, data: data).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'handling not implemented xml filename'
|
||||
|
||||
context "for 'primary' xml file" do
|
||||
include_context 'with primary xml file data'
|
||||
|
||||
it_behaves_like 'updating rpm xml file'
|
||||
end
|
||||
|
||||
context "for 'other' xml file" do
|
||||
include_context 'with other xml file data'
|
||||
|
||||
it_behaves_like 'updating rpm xml file'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_context 'when handling retried jobs' do |url|
|
||||
let(:set_name) { 'retry' }
|
||||
# Account for Sidekiq retry jitter
|
||||
# https://github.com/mperham/sidekiq/blob/3575ccb44c688dd08bfbfd937696260b12c622fb/lib/sidekiq/job_retry.rb#L217
|
||||
let(:schedule_jitter) { 10 }
|
||||
|
||||
# Try to mimic as closely as possible what Sidekiq will actually
|
||||
# do to retry a job.
|
||||
def retry_in(klass, time, args = 0)
|
||||
message = Gitlab::Json.generate(
|
||||
'class' => klass.name,
|
||||
'args' => [args],
|
||||
'retry' => true
|
||||
)
|
||||
|
||||
allow(klass).to receive(:sidekiq_retry_in_block).and_return(proc { time })
|
||||
|
||||
begin
|
||||
Sidekiq::JobRetry.new.local(klass, message, klass.queue) { raise 'boom' }
|
||||
rescue Sidekiq::JobRetry::Skip
|
||||
# Sidekiq scheduled the retry
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,52 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'handling rpm xml file' do
|
||||
include_context 'with rpm package data'
|
||||
|
||||
let(:xml) { nil }
|
||||
let(:data) { {} }
|
||||
|
||||
context 'when generate empty xml' do
|
||||
it 'generate expected xml' do
|
||||
expect(subject).to eq(empty_xml)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating existing xml' do
|
||||
let(:xml) { empty_xml }
|
||||
let(:data) { xml_update_params }
|
||||
|
||||
shared_examples 'changing root tag attribute' do
|
||||
it "increment previous 'packages' value by 1" do
|
||||
previous_value = Nokogiri::XML(xml).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
|
||||
new_value = Nokogiri::XML(subject).at(described_class::ROOT_TAG).attributes["packages"].value.to_i
|
||||
|
||||
expect(previous_value + 1).to eq(new_value)
|
||||
end
|
||||
end
|
||||
|
||||
it 'generate valid xml add expected xml node to existing xml' do
|
||||
# Have one root attribute
|
||||
result = Nokogiri::XML::Document.parse(subject).remove_namespaces!
|
||||
expect(result.children.count).to eq(1)
|
||||
|
||||
# Root node has 1 child with generated node
|
||||
expect(result.xpath("//#{described_class::ROOT_TAG}/package").count).to eq(1)
|
||||
end
|
||||
|
||||
context 'when empty xml' do
|
||||
it_behaves_like 'changing root tag attribute'
|
||||
end
|
||||
|
||||
context 'when xml has children' do
|
||||
let(:xml) { described_class.new(xml: empty_xml, data: data).execute }
|
||||
|
||||
it 'has children nodes' do
|
||||
result = Nokogiri::XML::Document.parse(xml).remove_namespaces!
|
||||
expect(result.children.count).to be > 0
|
||||
end
|
||||
|
||||
it_behaves_like 'changing root tag attribute'
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue