Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5b9147d835
commit
f962f7d316
|
|
@ -83,7 +83,6 @@ Gitlab/AvoidGitlabInstanceChecks:
|
|||
- 'ee/app/workers/gitlab_subscriptions/schedule_refresh_seats_worker.rb'
|
||||
- 'ee/app/workers/update_all_mirrors_worker.rb'
|
||||
- 'ee/lib/api/code_suggestions.rb'
|
||||
- 'ee/lib/api/internal/upcoming_reconciliations.rb'
|
||||
- 'ee/lib/api/scim/instance_scim.rb'
|
||||
- 'ee/lib/ee/api/namespaces.rb'
|
||||
- 'ee/lib/ee/gitlab/background_migration/backfill_project_statistics_storage_size_without_pipeline_artifacts_size_job.rb'
|
||||
|
|
|
|||
|
|
@ -3500,7 +3500,6 @@ Gitlab/BoundedContexts:
|
|||
- 'ee/app/services/timebox/report_service.rb'
|
||||
- 'ee/app/services/timebox_report_service.rb'
|
||||
- 'ee/app/services/todos/destroy/confidential_epic_service.rb'
|
||||
- 'ee/app/services/upcoming_reconciliations/update_service.rb'
|
||||
- 'ee/app/services/user_permissions/export_service.rb'
|
||||
- 'ee/app/services/users_ops_dashboard_projects/base_service.rb'
|
||||
- 'ee/app/services/users_ops_dashboard_projects/destroy_service.rb'
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ Layout/FirstHashElementIndentation:
|
|||
- 'ee/spec/models/concerns/elastic/note_spec.rb'
|
||||
- 'ee/spec/requests/api/analytics/project_deployment_frequency_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/mutations/iterations/create_spec.rb'
|
||||
- 'ee/spec/requests/api/internal/upcoming_reconciliations_spec.rb'
|
||||
- 'ee/spec/requests/api/merge_requests_spec.rb'
|
||||
- 'ee/spec/requests/ee/projects/deploy_tokens_controller_spec.rb'
|
||||
- 'ee/spec/requests/groups/group_members_controller_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1812,7 +1812,6 @@ Layout/LineLength:
|
|||
- 'ee/spec/requests/api/internal/app_sec/dast/site_validations_spec.rb'
|
||||
- 'ee/spec/requests/api/internal/base_spec.rb'
|
||||
- 'ee/spec/requests/api/internal/kubernetes_spec.rb'
|
||||
- 'ee/spec/requests/api/internal/upcoming_reconciliations_spec.rb'
|
||||
- 'ee/spec/requests/api/invitations_spec.rb'
|
||||
- 'ee/spec/requests/api/issues_spec.rb'
|
||||
- 'ee/spec/requests/api/iterations_spec.rb'
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ Performance/MapCompact:
|
|||
- 'ee/app/services/members/activate_service.rb'
|
||||
- 'ee/app/services/security/override_uuids_service.rb'
|
||||
- 'ee/app/services/security/store_scan_service.rb'
|
||||
- 'ee/app/services/upcoming_reconciliations/update_service.rb'
|
||||
- 'ee/app/services/vulnerabilities/findings/find_or_create_from_security_finding_service.rb'
|
||||
- 'ee/app/workers/geo/scheduler/scheduler_worker.rb'
|
||||
- 'ee/lib/banzai/filter/references/iteration_reference_filter.rb'
|
||||
|
|
|
|||
|
|
@ -758,7 +758,6 @@ RSpec/ContextWording:
|
|||
- 'ee/spec/services/system_notes/vulnerabilities_service_spec.rb'
|
||||
- 'ee/spec/services/timebox_report_service_spec.rb'
|
||||
- 'ee/spec/services/todo_service_spec.rb'
|
||||
- 'ee/spec/services/upcoming_reconciliations/update_service_spec.rb'
|
||||
- 'ee/spec/services/user_permissions/export_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerability_exports/export_service_spec.rb'
|
||||
- 'ee/spec/support/features/manual_quarterly_co_term_banner_examples.rb'
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ RSpec/ExpectChange:
|
|||
- 'ee/spec/requests/api/graphql/mutations/work_items/update_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/vulnerabilities/external_issue_links_spec.rb'
|
||||
- 'ee/spec/requests/api/groups_spec.rb'
|
||||
- 'ee/spec/requests/api/internal/upcoming_reconciliations_spec.rb'
|
||||
- 'ee/spec/requests/api/issues_spec.rb'
|
||||
- 'ee/spec/requests/api/provider_identity_spec.rb'
|
||||
- 'ee/spec/requests/groups/epics/related_epic_links_controller_spec.rb'
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ RSpec/ExpectInHook:
|
|||
- 'ee/spec/services/projects/create_from_template_service_spec.rb'
|
||||
- 'ee/spec/services/projects/mark_for_deletion_service_spec.rb'
|
||||
- 'ee/spec/services/projects/update_mirror_service_spec.rb'
|
||||
- 'ee/spec/services/upcoming_reconciliations/update_service_spec.rb'
|
||||
- 'ee/spec/support/shared_examples/lib/gitlab/graphql/issuables_lazy_links_aggregate_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/models/concerns/elastic/cannot_read_cross_project_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/models/concerns/verifiable_replicator_shared_examples.rb'
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ Style/SingleArgumentDig:
|
|||
- 'ee/spec/requests/api/graphql/project/dast_site_profile_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/project/dast_site_profiles_spec.rb'
|
||||
- 'ee/spec/requests/api/graphql/project/requirements_management/requirements_spec.rb'
|
||||
- 'ee/spec/requests/api/internal/upcoming_reconciliations_spec.rb'
|
||||
- 'ee/spec/services/vulnerabilities/findings/find_or_create_from_security_finding_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerabilities/manually_create_service_spec.rb'
|
||||
- 'lib/gitlab/auth/o_auth/auth_hash.rb'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Mark } from '@tiptap/core';
|
||||
import { Fragment } from '@tiptap/pm/model';
|
||||
import Code from '@tiptap/extension-code';
|
||||
import { EXTENSION_PRIORITY_LOWER } from '../constants';
|
||||
|
||||
|
|
@ -12,6 +13,18 @@ export default Code.extend({
|
|||
*/
|
||||
priority: EXTENSION_PRIORITY_LOWER,
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'code',
|
||||
preserveWhitespace: true,
|
||||
getContent(element, schema) {
|
||||
return Fragment.from(schema.text(element.textContent));
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
ArrowRight: () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { lowlight } from 'lowlight/lib/core';
|
||||
import { textblockTypeInputRule } from '@tiptap/core';
|
||||
import { Fragment } from '@tiptap/pm/model';
|
||||
import { base64DecodeUnicode } from '~/lib/utils/text_utility';
|
||||
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
|
||||
import languageLoader from '../services/code_block_language_loader';
|
||||
|
|
@ -49,8 +50,7 @@ export default CodeBlockHighlight.extend({
|
|||
const source = base64DecodeUnicode(
|
||||
element.dataset.diagramSrc.replace('data:text/plain;base64,', ''),
|
||||
);
|
||||
const node = schema.node('paragraph', {}, [schema.text(source)]);
|
||||
return node.content;
|
||||
return Fragment.from(schema.text(source));
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ const code = {
|
|||
close: generateCodeTag(closeTag),
|
||||
mixable: true,
|
||||
escape: false,
|
||||
expelEnclosingWhitespace: true,
|
||||
};
|
||||
|
||||
export default code;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export default {
|
|||
<template #default="{ mergeRequests, count, hasNextPage, loadMore, loading, error }">
|
||||
<collapsible-section :count="count" :loading="loading || error" :title="list.title">
|
||||
<div>
|
||||
<div class="gl-overflow-x-scroll">
|
||||
<div class="gl-overflow-x-auto">
|
||||
<table class="gl-w-full">
|
||||
<colgroup>
|
||||
<col style="width: 60px" />
|
||||
|
|
|
|||
|
|
@ -106,20 +106,20 @@ export default {
|
|||
<assigned-users :users="mergeRequest.reviewers.nodes" type="REVIEWERS" />
|
||||
</td>
|
||||
<td class="gl-py-4 gl-pl-3 gl-pr-5 gl-align-top">
|
||||
<div class="gl-flex gl-justify-end" :aria-label="statsAriaLabel">
|
||||
<div class="gl-flex gl-justify-end gl-gap-3" :aria-label="statsAriaLabel">
|
||||
<div class="gl-whitespace-nowrap">
|
||||
<gl-icon name="comments" class="!gl-align-middle" />
|
||||
{{ mergeRequest.userNotesCount }}
|
||||
</div>
|
||||
<div class="gl-ml-5 gl-whitespace-nowrap">
|
||||
<div class="gl-whitespace-nowrap">
|
||||
<gl-icon name="doc-code" />
|
||||
<span>{{ mergeRequest.diffStatsSummary.fileCount }}</span>
|
||||
</div>
|
||||
<div class="gl-ml-3 gl-flex gl-items-center gl-font-bold gl-text-green-600">
|
||||
<div class="gl-flex gl-items-center gl-font-bold gl-text-green-600">
|
||||
<span>+</span>
|
||||
<span>{{ mergeRequest.diffStatsSummary.additions }}</span>
|
||||
</div>
|
||||
<div class="gl-ml-3 gl-flex gl-items-center gl-font-bold gl-text-red-500">
|
||||
<div class="gl-flex gl-items-center gl-font-bold gl-text-red-500">
|
||||
<span>−</span>
|
||||
<span>{{ mergeRequest.diffStatsSummary.deletions }}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlFormGroup, GlFormInput } from '@gitlab/ui';
|
||||
import { localeDateFormat, isValidDate } from '~/lib/utils/datetime_utility';
|
||||
import {
|
||||
NEXT_CLEANUP_LABEL,
|
||||
NOT_SCHEDULED_POLICY_TEXT,
|
||||
|
|
@ -24,7 +25,11 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
parsedValue() {
|
||||
return this.enabled ? this.value : NOT_SCHEDULED_POLICY_TEXT;
|
||||
const date = new Date(this.value);
|
||||
const isValid = isValidDate(date);
|
||||
return this.enabled && isValid
|
||||
? localeDateFormat.asDateTimeFull.format(date)
|
||||
: NOT_SCHEDULED_POLICY_TEXT;
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@ export default {
|
|||
const urlPart = joinPaths(gon.relative_url_root || '', `/${this.fullPath}`, `/-/commits/`);
|
||||
return urlPart;
|
||||
},
|
||||
isLoading() {
|
||||
return this.$apollo.queries.project.loading;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async fetchContainingRefs({ query, namespace }) {
|
||||
|
|
@ -109,27 +112,29 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="gl-border-t">
|
||||
<refs-list
|
||||
v-if="hasBranches"
|
||||
:has-containing-refs="hasContainingBranches"
|
||||
:is-loading="$apollo.queries.project.loading"
|
||||
:tipping-refs="tippingBranches"
|
||||
:containing-refs="containingBranches"
|
||||
:namespace="$options.i18n.branches"
|
||||
:url-part="commitsUrlPart"
|
||||
:ref-type="$options.BRANCHES_REF_TYPE"
|
||||
@[$options.FETCH_CONTAINING_REFS_EVENT]="fetchContainingBranches"
|
||||
/>
|
||||
<refs-list
|
||||
v-if="hasTags"
|
||||
:has-containing-refs="hasContainingTags"
|
||||
:is-loading="$apollo.queries.project.loading"
|
||||
:tipping-refs="tippingTags"
|
||||
:containing-refs="containingTags"
|
||||
:namespace="$options.i18n.tags"
|
||||
:url-part="commitsUrlPart"
|
||||
:ref-type="$options.TAGS_REF_TYPE"
|
||||
@[$options.FETCH_CONTAINING_REFS_EVENT]="fetchContainingTags"
|
||||
/>
|
||||
<div class="well-segment">
|
||||
<refs-list
|
||||
:has-containing-refs="hasContainingBranches"
|
||||
:is-loading="isLoading"
|
||||
:tipping-refs="tippingBranches"
|
||||
:containing-refs="containingBranches"
|
||||
:namespace="$options.i18n.branches"
|
||||
:url-part="commitsUrlPart"
|
||||
:ref-type="$options.BRANCHES_REF_TYPE"
|
||||
@[$options.FETCH_CONTAINING_REFS_EVENT]="fetchContainingBranches"
|
||||
/>
|
||||
</div>
|
||||
<div class="well-segment">
|
||||
<refs-list
|
||||
:has-containing-refs="hasContainingTags"
|
||||
:is-loading="isLoading"
|
||||
:tipping-refs="tippingTags"
|
||||
:containing-refs="containingTags"
|
||||
:namespace="$options.i18n.tags"
|
||||
:url-part="commitsUrlPart"
|
||||
:ref-type="$options.TAGS_REF_TYPE"
|
||||
@[$options.FETCH_CONTAINING_REFS_EVENT]="fetchContainingTags"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
<script>
|
||||
import { GlCollapse, GlBadge, GlButton, GlIcon, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { CONTAINING_COMMIT, FETCH_CONTAINING_REFS_EVENT, BRANCHES_REF_TYPE } from '../constants';
|
||||
import { GlCollapse, GlBadge, GlButton, GlIcon, GlSkeletonLoader, GlLoadingIcon } from '@gitlab/ui';
|
||||
import {
|
||||
CONTAINING_COMMIT,
|
||||
FETCH_CONTAINING_REFS_EVENT,
|
||||
BRANCHES_REF_TYPE,
|
||||
EMPTY_BRANCHES_MESSAGE,
|
||||
EMPTY_TAGS_MESSAGE,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'RefsList',
|
||||
|
|
@ -10,6 +16,7 @@ export default {
|
|||
GlBadge,
|
||||
GlButton,
|
||||
GlIcon,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
urlPart: {
|
||||
|
|
@ -58,6 +65,15 @@ export default {
|
|||
refIcon() {
|
||||
return this.refType === BRANCHES_REF_TYPE ? 'branch' : 'tag';
|
||||
},
|
||||
showEmptyMessage() {
|
||||
return this.tippingRefs.length === 0 && this.containingRefs.length === 0 && !this.isLoading;
|
||||
},
|
||||
showNameSpace() {
|
||||
return (this.tippingRefs.length !== 0 || this.containingRefs.length !== 0) && !this.isLoading;
|
||||
},
|
||||
emptyMessage() {
|
||||
return this.refType === BRANCHES_REF_TYPE ? EMPTY_BRANCHES_MESSAGE : EMPTY_TAGS_MESSAGE;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleCollapse() {
|
||||
|
|
@ -78,9 +94,11 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="well-segment">
|
||||
<gl-icon :name="refIcon" class="gl-ml-2 gl-mr-3" />
|
||||
<span data-testid="title" class="gl-mr-2">{{ namespace }}</span>
|
||||
<div>
|
||||
<gl-icon :name="refIcon" :size="16" class="gl-ml-2 gl-mr-3" />
|
||||
<gl-loading-icon v-if="isLoading" size="sm" inline />
|
||||
<span v-if="showEmptyMessage">{{ emptyMessage }}</span>
|
||||
<span v-else-if="showNameSpace" data-testid="title" class="gl-mr-2">{{ namespace }}</span>
|
||||
<gl-badge
|
||||
v-for="ref in tippingRefs"
|
||||
:key="ref"
|
||||
|
|
|
|||
|
|
@ -21,3 +21,6 @@ export const FETCH_COMMIT_REFERENCES_ERROR = s__(
|
|||
export const BRANCHES_REF_TYPE = 'heads';
|
||||
|
||||
export const TAGS_REF_TYPE = 'tags';
|
||||
|
||||
export const EMPTY_BRANCHES_MESSAGE = __('No related branches found');
|
||||
export const EMPTY_TAGS_MESSAGE = __('No related tags found');
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
-# TODO: add fork suggestion (commits only)
|
||||
|
||||
%diff-file{ data: web_component_context }
|
||||
.rd-diff-file{ id: id, data: server_data }
|
||||
%diff-file{ id: id, data: server_data }
|
||||
.rd-diff-file
|
||||
= render RapidDiffs::DiffFileHeaderComponent.new(diff_file: @diff_file)
|
||||
.rd-diff-file-body
|
||||
-# TODO: replace with ViewComponents for each viewer
|
||||
= render 'projects/diffs/viewer', viewer: viewer
|
||||
= render viewer_component.new(diff_file: @diff_file)
|
||||
%diff-file-mounted
|
||||
|
|
|
|||
|
|
@ -17,23 +17,26 @@ module RapidDiffs
|
|||
project = @diff_file.repository.project
|
||||
params = tree_join(@diff_file.content_sha, @diff_file.file_path)
|
||||
{
|
||||
viewer: viewer_component.viewer_name,
|
||||
blob_diff_path: project_blob_diff_path(project, params)
|
||||
}
|
||||
end
|
||||
|
||||
def web_component_context
|
||||
viewer_name = viewer.partial_name
|
||||
if viewer_name == 'text'
|
||||
viewer_name = @parallel_view ? 'text_parallel' : 'text_inline'
|
||||
end
|
||||
def viewer_component
|
||||
# return Viewers::CollapsedComponent if collapsed?
|
||||
# return Viewers::NotDiffableComponent unless diffable?
|
||||
|
||||
{
|
||||
viewer: viewer_name
|
||||
}
|
||||
end
|
||||
is_text = @diff_file.text_diff?
|
||||
return Viewers::Text::ParallelViewComponent if is_text && @parallel_view
|
||||
return Viewers::Text::InlineViewComponent if is_text
|
||||
return Viewers::NoPreviewComponent if @diff_file.content_changed?
|
||||
|
||||
def viewer
|
||||
@diff_file.view_component_viewer
|
||||
# return Viewers::AddedComponent if new_file?
|
||||
# return Viewers::DeletedComponent if deleted_file?
|
||||
# return Viewers::RenamedComponent if renamed_file?
|
||||
# return Viewers::ModeChangedComponent if mode_changed?
|
||||
|
||||
Viewers::NoPreviewComponent
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
-# * toggle file comments
|
||||
-# * submodule compare
|
||||
|
||||
.rd-diff-file-header
|
||||
.rd-diff-file-header{ data: { testid: 'rd-diff-file-header' } }
|
||||
.rd-diff-file-title
|
||||
- if @diff_file.submodule?
|
||||
%span
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
= _("No preview for this file type")
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RapidDiffs
|
||||
module Viewers
|
||||
class NoPreviewComponent < ViewerComponent
|
||||
def self.viewer_name
|
||||
'no_preview'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RapidDiffs
|
||||
module Viewers
|
||||
module Text
|
||||
class DiffHunkComponent < ViewComponent::Base
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
|
||||
MAX_EXPANDABLE_LINES = 20
|
||||
|
||||
def initialize(diff_hunk:, diff_file:)
|
||||
@diff_hunk = diff_hunk
|
||||
@diff_file = diff_file
|
||||
end
|
||||
|
||||
def line_content(line)
|
||||
if line.blank?
|
||||
""
|
||||
else
|
||||
# `sub` and substring-ing would destroy HTML-safeness of `line`
|
||||
line[1, line.length]
|
||||
end
|
||||
end
|
||||
|
||||
def line_text(line)
|
||||
return unless line
|
||||
|
||||
line.rich_text ? line_content(line.rich_text) : line.text
|
||||
end
|
||||
|
||||
def line_link(line, position)
|
||||
return [] unless line && !line.meta?
|
||||
|
||||
line_number = position == :new ? line.new_pos : line.old_pos
|
||||
id = @diff_file.line_side_code(line, position)
|
||||
link = link_to line_number, "##{id}", { data: { line_number: line_number } }
|
||||
[link, id]
|
||||
end
|
||||
|
||||
def legacy_id(line)
|
||||
return unless line
|
||||
|
||||
@diff_file.line_code(line)
|
||||
end
|
||||
|
||||
def header_text
|
||||
@diff_hunk[:header].text
|
||||
end
|
||||
|
||||
def expand_buttons
|
||||
return render ExpandLinesComponent.new(direction: :both) if show_expand_both?
|
||||
|
||||
buttons = ''
|
||||
buttons += render ExpandLinesComponent.new(direction: :down) if show_expand_down?
|
||||
buttons += render ExpandLinesComponent.new(direction: :up) if show_expand_up?
|
||||
buttons
|
||||
end
|
||||
strong_memoize_attr :expand_buttons
|
||||
|
||||
private
|
||||
|
||||
def line_count_between
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def show_expand_both?
|
||||
line_count_between != 0 && line_count_between < MAX_EXPANDABLE_LINES
|
||||
end
|
||||
|
||||
def show_expand_down?
|
||||
@diff_hunk[:lines].empty? || @diff_hunk[:prev]
|
||||
end
|
||||
|
||||
def show_expand_up?
|
||||
!@diff_hunk[:header]&.index.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
%button
|
||||
= helpers.sprite_icon(icon_name)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RapidDiffs
|
||||
module Viewers
|
||||
module Text
|
||||
ICON_NAMES = {
|
||||
up: 'expand-up',
|
||||
down: 'expand-down',
|
||||
both: 'expand'
|
||||
}.freeze
|
||||
|
||||
class ExpandLinesComponent < ViewComponent::Base
|
||||
def initialize(direction:)
|
||||
@direction = direction
|
||||
end
|
||||
|
||||
def icon_name
|
||||
ICON_NAMES[@direction]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
- if @diff_hunk[:header]
|
||||
%tr
|
||||
%td{ colspan: '2' }= expand_buttons.html_safe
|
||||
%td= header_text
|
||||
|
||||
- @diff_hunk[:lines].each do |line|
|
||||
%tr
|
||||
- legacy_line_id = legacy_id(line)
|
||||
- link, id = line_link(line, :old)
|
||||
%td{ id: id, data: { legacy_id: legacy_line_id } }= link
|
||||
- link, id = line_link(line, :new)
|
||||
%td{ id: id, data: { legacy_id: legacy_line_id } }= link
|
||||
%td= line_text(line)
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RapidDiffs
|
||||
module Viewers
|
||||
module Text
|
||||
class InlineHunkComponent < DiffHunkComponent
|
||||
def line_count_between
|
||||
prev = @diff_hunk[:prev]
|
||||
return 0 if !prev || @diff_hunk[:lines].empty? || prev[:lines].empty?
|
||||
|
||||
@diff_hunk[:lines].first.old_pos - prev[:lines].last.old_pos
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
= render_parent
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RapidDiffs
|
||||
module Viewers
|
||||
module Text
|
||||
class InlineViewComponent < TextViewComponent
|
||||
def self.viewer_name
|
||||
'text_inline'
|
||||
end
|
||||
|
||||
def lines
|
||||
@diff_file.diff_lines_with_match_tail
|
||||
end
|
||||
|
||||
def diff_line(line)
|
||||
line
|
||||
end
|
||||
|
||||
def hunk_view_component
|
||||
InlineHunkComponent
|
||||
end
|
||||
|
||||
def column_titles
|
||||
[
|
||||
s_('RapidDiffs|Original line number'),
|
||||
s_('RapidDiffs|Diff line number'),
|
||||
s_('RapidDiffs|Diff line')
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
- if @diff_hunk[:header]
|
||||
%tr
|
||||
- 2.times do
|
||||
%td= expand_buttons.html_safe
|
||||
%td= header_text
|
||||
|
||||
- @diff_hunk[:lines].each do |pair|
|
||||
%tr
|
||||
- [pair[:left], pair[:right]].each_with_index do |line, index|
|
||||
- position = index == 0 ? :old : :new
|
||||
- link, id = line_link(line, position)
|
||||
%td{ id: id, data: { legacy_id: legacy_id(line) } }= link
|
||||
%td= line_text(line)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RapidDiffs
|
||||
module Viewers
|
||||
module Text
|
||||
class ParallelHunkComponent < DiffHunkComponent
|
||||
def line_count_between
|
||||
prev = @diff_hunk[:prev]
|
||||
return 0 if !prev || @diff_hunk[:lines].empty? || prev[:lines].empty?
|
||||
|
||||
first_pair = @diff_hunk[:lines].first
|
||||
first_line = first_pair[:left] || first_pair[:right]
|
||||
prev_pair = prev[:lines].last
|
||||
prev_line = prev_pair[:left] || prev_pair[:right]
|
||||
first_line.old_pos - prev_line.old_pos
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
= render_parent
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RapidDiffs
|
||||
module Viewers
|
||||
module Text
|
||||
class ParallelViewComponent < TextViewComponent
|
||||
def self.viewer_name
|
||||
'text_parallel'
|
||||
end
|
||||
|
||||
def lines
|
||||
@diff_file.parallel_diff_lines_with_match_tail
|
||||
end
|
||||
|
||||
# we need to iterate over diff lines to create an array of diff hunks
|
||||
# because parallel diffs can have empty sides we need to provide a line from a side that is not empty
|
||||
def diff_line(line)
|
||||
line[:left] || line[:right]
|
||||
end
|
||||
|
||||
def hunk_view_component
|
||||
ParallelHunkComponent
|
||||
end
|
||||
|
||||
def column_titles
|
||||
[
|
||||
s_('RapidDiffs|Original line number'),
|
||||
s_('RapidDiffs|Original line'),
|
||||
s_('RapidDiffs|Diff line number'),
|
||||
s_('RapidDiffs|Diff line')
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
%table
|
||||
%thead.gl-sr-only
|
||||
%tr
|
||||
- column_titles.each do |title|
|
||||
%th= title
|
||||
%tbody
|
||||
- diff_hunks.each do |diff_hunk|
|
||||
= render hunk_view(diff_hunk)
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RapidDiffs
|
||||
module Viewers
|
||||
module Text
|
||||
class TextViewComponent < ViewerComponent
|
||||
def lines
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def diff_line(line)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def hunk_view_component
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def column_titles
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def hunk_view(diff_hunk)
|
||||
hunk_view_component.new(
|
||||
diff_hunk: diff_hunk,
|
||||
diff_file: @diff_file
|
||||
)
|
||||
end
|
||||
|
||||
def diff_hunks
|
||||
return [] if lines.empty?
|
||||
|
||||
hunks = []
|
||||
current_hunk = nil
|
||||
|
||||
lines.each do |line|
|
||||
current_line = diff_line(line)
|
||||
is_match = current_line.type == 'match'
|
||||
|
||||
if is_match || current_hunk.nil?
|
||||
current_hunk = create_hunk(hunks.last, current_hunk, current_line, is_match, line)
|
||||
hunks << current_hunk
|
||||
else
|
||||
current_hunk[:lines] << line
|
||||
end
|
||||
end
|
||||
|
||||
hunks
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_hunk(prev, current_hunk, current_line, is_match, line)
|
||||
new_hunk = {
|
||||
header: is_match ? current_line : nil,
|
||||
lines: is_match ? [] : [line],
|
||||
prev: prev
|
||||
}
|
||||
|
||||
current_hunk[:next] = new_hunk if current_hunk
|
||||
new_hunk
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module RapidDiffs
|
||||
module Viewers
|
||||
class ViewerComponent < ViewComponent::Base
|
||||
def self.viewer_name
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def initialize(diff_file:)
|
||||
@diff_file = diff_file
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -601,16 +601,6 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
# TODO Remove in 16.0 when runners are known to send a system_id
|
||||
# For now, heartbeats with version updates might result in two Sidekiq jobs being queued if a runner has a system_id
|
||||
# This is not a problem since the jobs are deduplicated on the version
|
||||
def schedule_runner_version_update(new_version)
|
||||
return if Feature.enabled?(:hide_duplicate_runner_manager_fields_in_runner)
|
||||
return unless new_version && Gitlab::Ci::RunnerReleases.instance.enabled?
|
||||
|
||||
Ci::Runners::ProcessRunnerVersionUpdateWorker.perform_async(new_version)
|
||||
end
|
||||
|
||||
def prefix_for_new_and_legacy_runner
|
||||
return if registration_token_registration_type?
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
class CollectPipelineAnalyticsService
|
||||
TIME_BUCKETS_LIMIT = 1.week.in_hours + 1 # +1 to add some error margin
|
||||
|
||||
STATUS_GROUP_TO_STATUSES = { success: %w[success], failed: %w[failed], other: %w[canceled skipped] }.freeze
|
||||
STATUS_GROUPS = STATUS_GROUP_TO_STATUSES.keys.freeze
|
||||
STATUS_TO_STATUS_GROUP = STATUS_GROUP_TO_STATUSES.flat_map { |k, v| v.product([k]) }.to_h
|
||||
|
||||
def initialize(current_user:, project:, from_time:, to_time:, status_groups: [:all])
|
||||
@current_user = current_user
|
||||
@project = project
|
||||
@status_groups = status_groups
|
||||
@from_time = from_time || 1.week.ago.utc
|
||||
@to_time = to_time || Time.now.utc
|
||||
end
|
||||
|
||||
def execute
|
||||
return ServiceResponse.error(message: 'Project must be specified') unless @project
|
||||
|
||||
unless ::Gitlab::ClickHouse.configured?
|
||||
return ServiceResponse.error(message: 'ClickHouse database is not configured')
|
||||
end
|
||||
|
||||
return ServiceResponse.error(message: 'Not allowed') unless allowed?
|
||||
|
||||
if (@to_time - @from_time) / 1.hour > TIME_BUCKETS_LIMIT
|
||||
return ServiceResponse.error(message: "Maximum of #{TIME_BUCKETS_LIMIT} 1-hour intervals can be requested")
|
||||
end
|
||||
|
||||
ServiceResponse.success(payload: { aggregate: calculate_aggregate })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def allowed?
|
||||
@current_user&.can?(:read_ci_cd_analytics, @project)
|
||||
end
|
||||
|
||||
def clickhouse_model
|
||||
::ClickHouse::Models::Ci::FinishedPipelinesHourly
|
||||
end
|
||||
|
||||
def calculate_aggregate
|
||||
result = @status_groups.index_with(0)
|
||||
query = clickhouse_model.for_project(@project).within_dates(@from_time, @to_time)
|
||||
if @status_groups.include?(:all)
|
||||
all_query = query.select(query.count_pipelines_function.as('all'))
|
||||
result[:all] = ::ClickHouse::Client.select(all_query.to_sql, :main).first['all']
|
||||
end
|
||||
|
||||
if @status_groups.intersect?(STATUS_GROUPS)
|
||||
query = query
|
||||
.select(:status, query.count_pipelines_function.as('count'))
|
||||
.by_status(@status_groups.flat_map(&STATUS_GROUP_TO_STATUSES).compact)
|
||||
.group_by_status
|
||||
|
||||
result_by_status = ::ClickHouse::Client.select(query.to_sql, :main).map(&:values).to_h
|
||||
result_by_status.each_pair { |status, count| result[STATUS_TO_STATUS_GROUP[status]] += count }
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
= universal_stylesheet_link_tag 'application'
|
||||
= universal_stylesheet_link_tag 'application_utilities'
|
||||
= universal_stylesheet_link_tag 'tailwind'
|
||||
= universal_stylesheet_link_tag 'lookbook/rapid_diffs'
|
||||
= webpack_bundle_tag 'javascripts/entrypoints/lookbook/rapid_diffs'
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,13 @@
|
|||
%span.gl-text-primary= n_('parent', 'parents', @commit.parents.count)
|
||||
- @commit.parents.each do |parent|
|
||||
= link_to parent.short_id, project_commit_path(@project, parent), class: "commit-sha"
|
||||
#js-commit-branches-and-tags{ data: { full_path: @project.full_path, commit_sha: @commit.id } }
|
||||
#js-commit-branches-and-tags.gl-border-t{ data: { full_path: @project.full_path, commit_sha: @commit.short_id } }
|
||||
.well-segment
|
||||
= sprite_icon('branch', css_class: "gl-ml-2 gl-mr-3")
|
||||
= gl_loading_icon(inline: true, css_class: 'gl-align-middle')
|
||||
.well-segment
|
||||
= sprite_icon('tag', css_class: "gl-ml-2 gl-mr-3")
|
||||
= gl_loading_icon(inline: true, css_class: 'gl-align-middle')
|
||||
|
||||
.well-segment.merge-request-info
|
||||
.icon-container
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class RemoveExpiredMembersWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
namespace: member.source
|
||||
}
|
||||
with_context(context) do
|
||||
Members::DestroyService.new.execute(member, skip_authorization: true)
|
||||
Members::DestroyService.new.execute(member, skip_authorization: true, skip_subresources: true)
|
||||
|
||||
expired_user = member.user
|
||||
|
||||
|
|
|
|||
|
|
@ -5,3 +5,5 @@ feature_category: software_composition_analysis
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151444
|
||||
milestone: '17.0'
|
||||
queued_migration_version: 20240425205205
|
||||
finalize_after: '2024-05-06'
|
||||
finalized_by: 20240909204952
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FinalizeOsSbomComponentNormalizationMigration < Gitlab::Database::Migration[2.2]
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
milestone '17.4'
|
||||
|
||||
MIGRATION_NAME = 'RemoveNamespaceFromOsTypeSbomComponents'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: MIGRATION_NAME,
|
||||
table_name: :sbom_components,
|
||||
column_name: :id,
|
||||
job_arguments: [],
|
||||
finalize: true
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
8ca259a8eae0ae780f477b4ecf01ee29a0de5a3b053950b4f14dad0e80acba98
|
||||
|
|
@ -212,14 +212,13 @@ This list of limitations only reflects the latest version of GitLab. If you are
|
|||
[GitLab Environment Toolkit](https://gitlab.com/gitlab-org/gitlab-environment-toolkit) Terraform and Ansible scripts to deploy and operate production
|
||||
GitLab instances based on our [Reference Architectures](../reference_architectures/index.md), including automation of common daily tasks.
|
||||
[Epic 1465](https://gitlab.com/groups/gitlab-org/-/epics/1465) proposes to improve Geo installation even more.
|
||||
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** site.
|
||||
- Real-time updates of issues/merge requests (for example, via long polling) doesn't work on **secondary** sites where [http proxying is disabled](secondary_proxy/index.md#disable-secondary-site-http-proxying).
|
||||
- [Selective synchronization](replication/selective_synchronization.md) only limits what repositories and files are replicated. The entire PostgreSQL data is still replicated. Selective synchronization is not built to accommodate compliance / export control use cases.
|
||||
- [Pages access control](../../user/project/pages/pages_access_control.md) doesn't work on secondaries. See [GitLab issue #9336](https://gitlab.com/gitlab-org/gitlab/-/issues/9336) for details.
|
||||
- [Disaster recovery](disaster_recovery/index.md) for deployments that have multiple secondary sites causes downtime due to the need to perform complete re-synchronization and re-configuration of all non-promoted secondaries to follow the new primary site.
|
||||
- [Disaster recovery](disaster_recovery/index.md) for deployments that have multiple secondary sites causes downtime due to the need to re-initialize PostgreSQL streaming replication on all non-promoted secondaries to follow the new primary site.
|
||||
- For Git over SSH, to make the project clone URL display correctly regardless of which site you are browsing, secondary sites must use the same port as the primary. [GitLab issue #339262](https://gitlab.com/gitlab-org/gitlab/-/issues/339262) proposes to remove this limitation.
|
||||
- Git push over SSH against a secondary site does not work for pushes over 1.86 GB. [GitLab issue #413109](https://gitlab.com/gitlab-org/gitlab/-/issues/413109) tracks this bug.
|
||||
- Backups [cannot be run on secondaries](replication/troubleshooting/replication.md#message-error-canceling-statement-due-to-conflict-with-recovery).
|
||||
- Git clone and fetch requests with option `--depth` over SSH against a secondary site does not work and hangs indefinitely if the secondary site is not up to date at the time the request is initiated. For more information, see [issue 391980](https://gitlab.com/gitlab-org/gitlab/-/issues/391980).
|
||||
- Backups [cannot be run on Geo secondary sites](replication/troubleshooting/replication.md#message-error-canceling-statement-due-to-conflict-with-recovery).
|
||||
- Git push with options over SSH against a secondary site does not work and terminates the connection. For more information, see [issue 417186](https://gitlab.com/gitlab-org/gitlab/-/issues/417186).
|
||||
- The Geo secondary site does not accelerate (serve) the clone request for the first stage of the pipeline in most cases. Later stages are not guaranteed to be served by the secondary site either, for example if the Git change is large, bandwidth is small, or pipeline stages are short. In general, it does serve the clone request for subsequent stages. [Issue 446176](https://gitlab.com/gitlab-org/gitlab/-/issues/446176) discusses the reasons for this and proposes an enhancement to increase the chance that Runner clone requests are served from the secondary site.
|
||||
- When a single Git repository receives pushes at a high-enough rate, the secondary site's local copy can be perpetually out-of-date. This causes all Git fetches of that repository to be forwarded to the primary site. See [GitLab issue #455870](https://gitlab.com/gitlab-org/gitlab/-/issues/455870).
|
||||
|
|
@ -228,6 +227,8 @@ This list of limitations only reflects the latest version of GitLab. If you are
|
|||
- GitLab Pages - should always use a separate domain, as part of [the prerequisites for running GitLab Pages](../pages/index.md#prerequisites).
|
||||
- With a [unified URL](secondary_proxy/index.md#set-up-a-unified-url-for-geo-sites), Let's Encrypt can't generate certificates unless it can reach both IPs through the same domain. To use TLS certificates with Let's Encrypt, you can manually point the domain to one of the Geo sites, generate the certificate, then copy it to all other sites.
|
||||
- When a [secondary site uses a separate URL](secondary_proxy/index.md#set-up-a-separate-url-for-a-secondary-geo-site) from the primary site, [signing in the secondary site using SAML](replication/single_sign_on.md#saml-with-separate-url-with-proxying-enabled) is only supported if the SAML Identity Provider (IdP) allows an application to be configured with multiple callback URLs.
|
||||
- Git clone and fetch requests with option `--depth` over SSH against a secondary site does not work and hangs indefinitely if the secondary site is not up to date at the time the request is initiated. This is due to problems related to translating Git SSH to Git https during proxying. For more information, see [issue 391980](https://gitlab.com/gitlab-org/gitlab/-/issues/391980). A new workflow that does not involve the aforementioned translation step is now available for Linux-packaged GitLab Geo secondary sites which can be enabled with a feature flag. For more details, see [comment in issue 454707](https://gitlab.com/gitlab-org/gitlab/-/issues/454707#note_2102067451). The fix for Cloud Native GitLab Geo secondary sites is tracked in [issue 5641](https://gitlab.com/gitlab-org/charts/gitlab/-/issues/5641).
|
||||
- Some customers have reported that `git fetch` over SSH when the secondary site is out of date hangs and/or times out and fails. `git clone` requests over SSH are not impacted. For more information, see [issue 454707](https://gitlab.com/gitlab-org/gitlab/-/issues/454707). A fix available for Linux-packaged GitLab Geo secondary sites which can be enabled with a feature flag. For more details, see [comment in issue 454707](https://gitlab.com/gitlab-org/gitlab/-/issues/454707#note_2102067451). The fix for Cloud Native GitLab Geo secondary sites is tracked in [issue 5641](https://gitlab.com/gitlab-org/charts/gitlab/-/issues/5641).
|
||||
|
||||
### Limitations on replication/verification
|
||||
|
||||
|
|
|
|||
|
|
@ -156,7 +156,6 @@ You can keep track of the progress to implement the missing items in
|
|||
these epics/issues:
|
||||
|
||||
- [Geo: Improve the self-service Geo replication framework](https://gitlab.com/groups/gitlab-org/-/epics/3761)
|
||||
- [Geo: Move existing blobs to framework](https://gitlab.com/groups/gitlab-org/-/epics/3588)
|
||||
|
||||
### Replicated data types behind a feature flag
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
|
|
@ -64,7 +64,7 @@ would be helpful if one can have a shell to aid debugging. When a job is
|
|||
running, on the right panel you can see a button `debug` that opens the terminal
|
||||
for the current job. Only the person who started a job can debug it.
|
||||
|
||||

|
||||

|
||||
|
||||
When selected, a new tab opens to the terminal page where you can access
|
||||
the terminal and type commands like in a standard shell.
|
||||
|
|
|
|||
|
|
@ -694,67 +694,3 @@ Example response:
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Upcoming Reconciliations (deprecated)
|
||||
|
||||
These endpoints have been replaced with the ones in the [Internal Upcoming Reconciliations API](#upcoming-reconciliations).
|
||||
|
||||
### Update `upcoming_reconciliations`
|
||||
|
||||
Use a PUT command to update `upcoming_reconciliations`.
|
||||
|
||||
```plaintext
|
||||
PUT /internal/upcoming_reconciliations
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:-------------------|:-----------|:---------|:------------|
|
||||
| `upcoming_reconciliations` | array | yes | Array of upcoming reconciliations |
|
||||
|
||||
Each array element contains:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:-------------------|:-----------|:---------|:------------|
|
||||
| `namespace_id` | integer | yes | ID of the namespace to be reconciled |
|
||||
| `next_reconciliation_date` | date | yes | Date of the next reconciliation |
|
||||
| `display_alert_from` | date | yes | Start date to display alert of upcoming reconciliation |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <admin_access_token>" --header "Content-Type: application/json" \
|
||||
--data '{"upcoming_reconciliations": [{"namespace_id": 127, "next_reconciliation_date": "13 Jun 2021", "display_alert_from": "06 Jun 2021"}, {"namespace_id": 129, "next_reconciliation_date": "12 Jun 2021", "display_alert_from": "05 Jun 2021"}]}' \
|
||||
"https://gitlab.com/api/v4/internal/upcoming_reconciliations"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```plaintext
|
||||
200
|
||||
```
|
||||
|
||||
### Delete an `upcoming_reconciliation`
|
||||
|
||||
Use a DELETE command to delete an `upcoming_reconciliation`.
|
||||
|
||||
```plaintext
|
||||
DELETE /internal/upcoming_reconciliations
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:---------------|:--------|:---------|:----------------------------------------------------------------------------------|
|
||||
| `namespace_id` | integer | yes | The ID of the GitLab.com namespace that no longer has an upcoming reconciliation. |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request DELETE \
|
||||
--url "http://localhost:3000/api/v4/internal/upcoming_reconciliations?namespace_id=22" \
|
||||
--header 'PRIVATE-TOKEN: <admin_access_token>'
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```plaintext
|
||||
204
|
||||
```
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ DETAILS:
|
|||
**Tier:** Free, Premium, Ultimate
|
||||
**Offering:** Self-managed
|
||||
|
||||
This page contains information about the minimum system requirements to install GitLab.
|
||||
This page contains information about the system requirements to install GitLab.
|
||||
|
||||
## Hardware
|
||||
|
||||
|
|
@ -45,8 +45,9 @@ For a maximum of 20 requests per second or 1,000 users, you should have 16 GB of
|
|||
For more users or higher workload,
|
||||
see [reference architectures](../administration/reference_architectures/index.md).
|
||||
|
||||
In certain circumstances, GitLab might run in a
|
||||
[memory-constrained environment](https://docs.gitlab.com/omnibus/settings/memory_constrained_envs.html).
|
||||
In some cases, GitLab can run with at least 8 GB of memory.
|
||||
For more information, see
|
||||
[running GitLab in a memory-constrained environment](https://docs.gitlab.com/omnibus/settings/memory_constrained_envs.html).
|
||||
|
||||
## Database
|
||||
|
||||
|
|
|
|||
|
|
@ -329,6 +329,7 @@ You can sort members by **Account**, **Access granted**, **Role**, or **Last sig
|
|||
## Add users to a group
|
||||
|
||||
> - Expiring access email notification [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12704) in GitLab 16.2.
|
||||
> - Access expiration date for direct members of subgroups and projects [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/471051) in GitLab 17.4.
|
||||
|
||||
You can give a user access to all projects in a group.
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ In the above example:
|
|||
## Add users to a project
|
||||
|
||||
> - Expiring access email notification [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12704) in GitLab 16.2.
|
||||
> - Access expiration date for direct members of subgroups and projects [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/471051) in GitLab 17.4.
|
||||
|
||||
Add users to a project so they become direct members and have permission
|
||||
to perform actions.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,18 @@ module ClickHouse # rubocop:disable Gitlab/BoundedContexts -- Existing module
|
|||
where(path: project.project_namespace.traversal_path)
|
||||
end
|
||||
|
||||
def within_dates(from_time, to_time)
|
||||
query = self
|
||||
started_at_bucket = @query_builder.table[:started_at_bucket]
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord -- this is a ClickHouse model
|
||||
query = query.where(started_at_bucket.gteq(format_time(from_time))) if from_time
|
||||
query = query.where(started_at_bucket.lt(format_time(to_time))) if to_time
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
query
|
||||
end
|
||||
|
||||
def by_status(statuses)
|
||||
where(status: statuses)
|
||||
end
|
||||
|
|
@ -31,6 +43,16 @@ module ClickHouse # rubocop:disable Gitlab/BoundedContexts -- Existing module
|
|||
def count_pipelines_function
|
||||
Arel::Nodes::NamedFunction.new('countMerge', [@query_builder.table[:count_pipelines]])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format_time(date)
|
||||
Arel::Nodes::NamedFunction.new('toDateTime64', [
|
||||
Arel::Nodes::SqlLiteral.new(date.utc.strftime("'%Y-%m-%d %H:%M:%S'")),
|
||||
6,
|
||||
Arel::Nodes.build_quoted('UTC')
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Backup
|
||||
class JsonLogger < Gitlab::JsonLogger
|
||||
exclude_context!
|
||||
|
||||
def self.file_name_noext
|
||||
'backup_json'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,39 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
class BackupLogger < Gitlab::JsonLogger
|
||||
exclude_context!
|
||||
|
||||
attr_reader :progress
|
||||
class BackupLogger
|
||||
attr_reader :progress, :json_logger
|
||||
|
||||
def initialize(progress)
|
||||
@progress = progress
|
||||
@json_logger = ::Gitlab::Backup::JsonLogger.build
|
||||
end
|
||||
|
||||
def warn(message)
|
||||
progress.puts Rainbow("#{Time.zone.now} -- #{message}").yellow
|
||||
|
||||
super
|
||||
json_logger.warn(message: message)
|
||||
end
|
||||
|
||||
def info(message)
|
||||
progress.puts Rainbow("#{Time.zone.now} -- #{message}").cyan
|
||||
|
||||
super
|
||||
json_logger.info(message: message)
|
||||
end
|
||||
|
||||
def error(message)
|
||||
progress.puts Rainbow("#{Time.zone.now} -- #{message}").red
|
||||
|
||||
super
|
||||
json_logger.error(message: message)
|
||||
end
|
||||
|
||||
def flush
|
||||
progress.flush
|
||||
end
|
||||
|
||||
def self.file_name_noext
|
||||
'backup_json'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -76,6 +76,14 @@ module Gitlab
|
|||
Gitlab::Git.diff_line_code(file_path, line.new_pos, line.old_pos)
|
||||
end
|
||||
|
||||
def line_side_code(line, position)
|
||||
return if line.meta?
|
||||
|
||||
prefix = position == :old ? "L" : "R"
|
||||
number = position == :old ? line.old_pos : line.new_pos
|
||||
"line_#{file_hash}_#{prefix}#{number}"
|
||||
end
|
||||
|
||||
def line_for_line_code(code)
|
||||
diff_lines.find { |line| line_code(line) == code }
|
||||
end
|
||||
|
|
@ -266,6 +274,10 @@ module Gitlab
|
|||
@parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize
|
||||
end
|
||||
|
||||
def parallel_diff_lines_with_match_tail
|
||||
@parallel_diff_lines_with_match_tail ||= Gitlab::Diff::ParallelDiff.new(self).parallelize(diff_lines_with_match_tail)
|
||||
end
|
||||
|
||||
def raw_diff
|
||||
diff.diff.to_s
|
||||
end
|
||||
|
|
@ -289,6 +301,7 @@ module Gitlab
|
|||
def file_hash
|
||||
Digest::SHA1.hexdigest(file_path)
|
||||
end
|
||||
strong_memoize_attr :file_hash
|
||||
|
||||
def added_lines
|
||||
strong_memoize(:added_lines) do
|
||||
|
|
@ -387,11 +400,6 @@ module Gitlab
|
|||
@rich_viewer = rich_viewer_class&.new(self)
|
||||
end
|
||||
|
||||
# This is going to be updated with viewer components
|
||||
def view_component_viewer
|
||||
has_renderable? ? rendered.viewer : viewer
|
||||
end
|
||||
|
||||
def alternate_viewer
|
||||
alternate_viewer_class&.new(self)
|
||||
end
|
||||
|
|
@ -403,23 +411,29 @@ module Gitlab
|
|||
# This adds the bottom match line to the array if needed. It contains
|
||||
# the data to load more context lines.
|
||||
def diff_lines_for_serializer
|
||||
strong_memoize(:diff_lines_for_serializer) do
|
||||
lines = highlighted_diff_lines
|
||||
lines = diff_lines_with_match_tail
|
||||
return if lines.empty?
|
||||
|
||||
next if lines.empty?
|
||||
next if blob.nil?
|
||||
|
||||
last_line = lines.last
|
||||
|
||||
if last_line.new_pos < total_blob_lines(blob) && !deleted_file?
|
||||
match_line = Gitlab::Diff::Line.new("", 'match', nil, last_line.old_pos, last_line.new_pos)
|
||||
lines.push(match_line)
|
||||
end
|
||||
|
||||
lines
|
||||
end
|
||||
lines
|
||||
end
|
||||
|
||||
def diff_lines_with_match_tail
|
||||
lines = highlighted_diff_lines
|
||||
|
||||
return [] if lines.empty?
|
||||
return [] if blob.nil?
|
||||
|
||||
last_line = lines.last
|
||||
|
||||
if last_line.new_pos < total_blob_lines(blob) && !deleted_file?
|
||||
match_line = Gitlab::Diff::Line.new("", 'match', nil, last_line.old_pos, last_line.new_pos)
|
||||
lines.push(match_line)
|
||||
end
|
||||
|
||||
lines
|
||||
end
|
||||
strong_memoize_attr(:diff_lines_with_match_tail)
|
||||
|
||||
def fully_expanded?
|
||||
return true if binary?
|
||||
|
||||
|
|
@ -449,6 +463,10 @@ module Gitlab
|
|||
diffable? && !deleted_file?
|
||||
end
|
||||
|
||||
def text_diff?
|
||||
modified_file? && text?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def diffable_by_attribute?
|
||||
|
|
|
|||
|
|
@ -9,13 +9,12 @@ module Gitlab
|
|||
@diff_file = diff_file
|
||||
end
|
||||
|
||||
def parallelize
|
||||
def parallelize(diff_lines = diff_file.highlighted_diff_lines)
|
||||
i = 0
|
||||
free_right_index = nil
|
||||
|
||||
lines = []
|
||||
highlighted_diff_lines = diff_file.highlighted_diff_lines
|
||||
highlighted_diff_lines.each do |line|
|
||||
diff_lines.each do |line|
|
||||
if line.removed?
|
||||
lines << {
|
||||
left: line,
|
||||
|
|
|
|||
|
|
@ -16276,6 +16276,9 @@ msgstr ""
|
|||
msgid "CreateValueStreamForm|'%{name}' Value Stream has been successfully created."
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|'%{name}' Value Stream has been successfully saved."
|
||||
msgstr ""
|
||||
|
||||
msgid "CreateValueStreamForm|'%{name}' Value Stream saved"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -26363,9 +26366,6 @@ msgstr ""
|
|||
msgid "GroupSelect|Select a group"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings| %{link_start}What do experiment and beta mean?%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|'%{name}' has been scheduled for removal on %{date}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -26462,9 +26462,6 @@ msgstr ""
|
|||
msgid "GroupSettings|Enable sending email notifications for this group and all its subgroups and projects"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Enabling these features is your acceptance of the %{link_start}GitLab Testing Agreement%{link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Enforce SSH Certificates"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -26576,9 +26573,6 @@ msgstr ""
|
|||
msgid "GroupSettings|There was a problem updating the group CI/CD settings: %{error_messages}."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|These features are being developed and might be unstable."
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -36153,6 +36147,12 @@ msgstr ""
|
|||
msgid "No regions configured"
|
||||
msgstr ""
|
||||
|
||||
msgid "No related branches found"
|
||||
msgstr ""
|
||||
|
||||
msgid "No related tags found"
|
||||
msgstr ""
|
||||
|
||||
msgid "No repository"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -44652,6 +44652,18 @@ msgstr ""
|
|||
msgid "Random"
|
||||
msgstr ""
|
||||
|
||||
msgid "RapidDiffs|Diff line"
|
||||
msgstr ""
|
||||
|
||||
msgid "RapidDiffs|Diff line number"
|
||||
msgstr ""
|
||||
|
||||
msgid "RapidDiffs|Original line"
|
||||
msgstr ""
|
||||
|
||||
msgid "RapidDiffs|Original line number"
|
||||
msgstr ""
|
||||
|
||||
msgid "Rate Limits"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "factory_bot"
|
||||
|
||||
module RapidDiffs
|
||||
class DiffFileComponentPreview < ViewComponent::Preview
|
||||
layout 'lookbook/rapid_diffs'
|
||||
|
|
@ -9,7 +7,7 @@ module RapidDiffs
|
|||
# @!group Code
|
||||
|
||||
def added_and_removed_lines
|
||||
diff = "
|
||||
hunk = "
|
||||
--- a/app/views/layouts/preview/rapid_diffs.html.haml (revision eaba934a0bc6eed56cfd1f082e9fa3f5409f2938)
|
||||
+++ b/app/views/layouts/preview/rapid_diffs.html.haml (date 1718119822001)
|
||||
@@ -1,7 +1,6 @@
|
||||
|
|
@ -23,11 +21,11 @@ module RapidDiffs
|
|||
+%div{ style: 'padding: 20px', class: 'container-fluid' }
|
||||
= yield
|
||||
"
|
||||
render(RapidDiffs::DiffFileComponent.new(diff_file: diff_file(diff)))
|
||||
render(::RapidDiffs::DiffFileComponent.new(diff_file: diff_file(hunk)))
|
||||
end
|
||||
|
||||
def added_lines
|
||||
diff = "
|
||||
hunk = "
|
||||
--- a/app/views/layouts/preview/rapid_diffs.html.haml (revision eaba934a0bc6eed56cfd1f082e9fa3f5409f2938)
|
||||
+++ b/app/views/layouts/preview/rapid_diffs.html.haml (date 1718118441569)
|
||||
@@ -2,6 +2,7 @@
|
||||
|
|
@ -39,11 +37,11 @@ module RapidDiffs
|
|||
%div{ style: 'padding: 20px' }
|
||||
= yield
|
||||
"
|
||||
render(RapidDiffs::DiffFileComponent.new(diff_file: diff_file(diff)))
|
||||
render(::RapidDiffs::DiffFileComponent.new(diff_file: diff_file(hunk)))
|
||||
end
|
||||
|
||||
def removed_lines
|
||||
diff = "
|
||||
hunk = "
|
||||
--- a/app/views/layouts/preview/rapid_diffs.html.haml (revision eaba934a0bc6eed56cfd1f082e9fa3f5409f2938)
|
||||
+++ b/app/views/layouts/preview/rapid_diffs.html.haml (date 1718119765262)
|
||||
@@ -1,7 +1,6 @@
|
||||
|
|
@ -55,11 +53,11 @@ module RapidDiffs
|
|||
%div{ style: 'padding: 20px' }
|
||||
= yield
|
||||
"
|
||||
render(RapidDiffs::DiffFileComponent.new(diff_file: diff_file(diff)))
|
||||
render(::RapidDiffs::DiffFileComponent.new(diff_file: diff_file(hunk)))
|
||||
end
|
||||
|
||||
def added_file
|
||||
diff = "
|
||||
hunk = "
|
||||
--- /dev/null
|
||||
+++ b/app/views/layouts/preview/rapid_diffs.html.haml (date 1718119765262)
|
||||
@@ -0,0 +1,7 @@
|
||||
|
|
@ -71,11 +69,11 @@ module RapidDiffs
|
|||
+%div{ style: 'padding: 20px' }
|
||||
+ = yield
|
||||
"
|
||||
render(RapidDiffs::DiffFileComponent.new(diff_file: diff_file(diff)))
|
||||
render(::RapidDiffs::DiffFileComponent.new(diff_file: diff_file(hunk)))
|
||||
end
|
||||
|
||||
def removed_file
|
||||
diff = "
|
||||
hunk = "
|
||||
--- a/app/views/layouts/preview/rapid_diffs.html.haml (revision eaba934a0bc6eed56cfd1f082e9fa3f5409f2938)
|
||||
+++ /dev/null
|
||||
@@ -1,7 +1,0 @@
|
||||
|
|
@ -87,29 +85,31 @@ module RapidDiffs
|
|||
-%div{ style: 'padding: 20px' }
|
||||
- = yield
|
||||
"
|
||||
render(RapidDiffs::DiffFileComponent.new(diff_file: diff_file(diff)))
|
||||
render(::RapidDiffs::DiffFileComponent.new(diff_file: diff_file(hunk)))
|
||||
end
|
||||
|
||||
# @!endgroup
|
||||
|
||||
private
|
||||
|
||||
def diff_content(diff)
|
||||
diff.split("\n").filter_map(&:lstrip).reject(&:empty?).join("\n")
|
||||
end
|
||||
|
||||
def diff_file(diff)
|
||||
::Gitlab::Diff::File.new(raw_diff(diff_content(diff)), repository: FakeRepository.new).tap do |file|
|
||||
file.instance_variable_set(:@new_blob, Blob.decorate(raw_blob(diff_content(diff))))
|
||||
def diff_file(hunk)
|
||||
diff = raw_diff(diff_content(hunk))
|
||||
diff_refs = ::Gitlab::Diff::DiffRefs.new(base_sha: 'a', head_sha: 'b')
|
||||
::Gitlab::Diff::File.new(diff, repository: FakeRepository.new, diff_refs: diff_refs).tap do |file|
|
||||
file.instance_variable_set(:@new_blob, Blob.decorate(raw_blob(diff_content(hunk))))
|
||||
end
|
||||
end
|
||||
|
||||
def raw_diff(diff)
|
||||
Gitlab::Git::Diff.new(
|
||||
def diff_content(hunk)
|
||||
hunk.split("\n").filter_map(&:lstrip).reject(&:empty?).join("\n")
|
||||
end
|
||||
|
||||
def raw_diff(hunk)
|
||||
::Gitlab::Git::Diff.new(
|
||||
{
|
||||
diff: diff,
|
||||
new_path: new_path(diff),
|
||||
old_path: old_path(diff),
|
||||
diff: hunk,
|
||||
new_path: new_path(hunk),
|
||||
old_path: old_path(hunk),
|
||||
a_mode: '0',
|
||||
b_mode: '100644',
|
||||
new_file: true,
|
||||
|
|
@ -119,24 +119,24 @@ module RapidDiffs
|
|||
})
|
||||
end
|
||||
|
||||
def raw_blob(diff)
|
||||
Gitlab::Git::Blob.new(
|
||||
def raw_blob(hunk)
|
||||
::Gitlab::Git::Blob.new(
|
||||
id: 'bba46076dd3e6a406b45ad98ef3b8194fde8b568',
|
||||
commit_id: 'master',
|
||||
size: 264,
|
||||
name: new_path(diff),
|
||||
path: new_path(diff),
|
||||
name: new_path(hunk),
|
||||
path: new_path(hunk),
|
||||
data: "",
|
||||
mode: '100644'
|
||||
)
|
||||
end
|
||||
|
||||
def old_path(diff)
|
||||
diff[%r{--- a/([^\s\n]*)}, 1]
|
||||
def old_path(hunk)
|
||||
hunk[%r{--- a/([^\s\n]*)}, 1]
|
||||
end
|
||||
|
||||
def new_path(diff)
|
||||
diff[%r{\+\+\+ b/([^\s\n]*)}, 1]
|
||||
def new_path(hunk)
|
||||
hunk[%r{\+\+\+ b/([^\s\n]*)}, 1]
|
||||
end
|
||||
|
||||
class FakeRepository
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::DiffFileComponent, type: :component, feature_category: :code_review_workflow do
|
||||
let_it_be(:diff_file) { build(:diff_file) }
|
||||
let(:repository) { diff_file.repository }
|
||||
let(:project) { repository.container }
|
||||
let(:namespace) { project.namespace }
|
||||
let(:web_component) { page.find('diff-file') }
|
||||
|
||||
it "renders" do
|
||||
render_component
|
||||
expect(page).to have_selector('diff-file')
|
||||
expect(page).to have_selector('diff-file-mounted')
|
||||
end
|
||||
|
||||
it "renders server data" do
|
||||
render_component
|
||||
diff_path = "/#{namespace.to_param}/#{project.to_param}/-/blob/#{diff_file.content_sha}/#{diff_file.file_path}/diff"
|
||||
expect(web_component['data-blob-diff-path']).to eq(diff_path)
|
||||
end
|
||||
|
||||
context "when is text diff" do
|
||||
before do
|
||||
allow(diff_file).to receive(:text_diff?).and_return(true)
|
||||
end
|
||||
|
||||
it "renders parallel text viewer" do
|
||||
render_component
|
||||
expect(web_component['data-viewer']).to eq('text_inline')
|
||||
end
|
||||
|
||||
it "renders parallel text viewer" do
|
||||
render_component(parallel_view: true)
|
||||
expect(web_component['data-viewer']).to eq('text_parallel')
|
||||
end
|
||||
end
|
||||
|
||||
context "when non-text content changed" do
|
||||
before do
|
||||
allow(diff_file).to receive(:text_diff?).and_return(false)
|
||||
allow(diff_file).to receive(:content_changed?).and_return(true)
|
||||
end
|
||||
|
||||
it "renders no preview" do
|
||||
render_component
|
||||
expect(web_component['data-viewer']).to eq('no_preview')
|
||||
end
|
||||
end
|
||||
|
||||
context "when no viewer found" do
|
||||
before do
|
||||
allow(diff_file).to receive(:text_diff?).and_return(false)
|
||||
allow(diff_file).to receive(:content_changed?).and_return(false)
|
||||
end
|
||||
|
||||
it "renders no preview" do
|
||||
render_component
|
||||
expect(web_component['data-viewer']).to eq('no_preview')
|
||||
end
|
||||
end
|
||||
|
||||
def render_component(**args)
|
||||
render_inline(described_class.new(diff_file: diff_file, **args))
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::DiffFileHeaderComponent, type: :component, feature_category: :code_review_workflow do
|
||||
let_it_be(:diff_file) { build(:diff_file) }
|
||||
let(:header) { page.find('[data-testid="rd-diff-file-header"]') }
|
||||
|
||||
it "renders file path" do
|
||||
render_component
|
||||
expect(header).to have_text(diff_file.file_path)
|
||||
end
|
||||
|
||||
it "renders submodule info" do
|
||||
allow(diff_file).to receive(:submodule?).and_return(true)
|
||||
allow_next_instance_of(SubmoduleHelper) do |instance|
|
||||
allow(instance).to receive(:submodule_links).and_return(nil)
|
||||
end
|
||||
render_component
|
||||
expect(page.find('svg use')['href']).to include('folder-git')
|
||||
expect(page).to have_text(diff_file.blob.name)
|
||||
expect(page).to have_text(diff_file.blob.id[0..7])
|
||||
end
|
||||
|
||||
it "renders path change" do
|
||||
allow(diff_file).to receive(:renamed_file?).and_return(true)
|
||||
allow(diff_file).to receive(:old_path).and_return('old/path')
|
||||
allow(diff_file).to receive(:new_path).and_return('new/path')
|
||||
render_component
|
||||
expect(header).to have_text('old/path')
|
||||
expect(header).to have_text('new/path')
|
||||
end
|
||||
|
||||
it "renders mode change" do
|
||||
allow(diff_file).to receive(:mode_changed?).and_return(true)
|
||||
render_component
|
||||
expect(header).to have_text("#{diff_file.a_mode} → #{diff_file.b_mode}")
|
||||
end
|
||||
|
||||
it "renders deleted message" do
|
||||
allow(diff_file).to receive(:deleted_file?).and_return(true)
|
||||
render_component
|
||||
expect(header).to have_text('deleted')
|
||||
end
|
||||
|
||||
it "renders LFS message" do
|
||||
allow(diff_file).to receive(:stored_externally?).and_return(true)
|
||||
allow(diff_file).to receive(:external_storage).and_return(:lfs)
|
||||
render_component
|
||||
expect(header).to have_text('LFS')
|
||||
end
|
||||
|
||||
it "renders line count" do
|
||||
render_component
|
||||
expect(page.find('[data-testid="js-file-addition-line"]')).to have_text(diff_file.added_lines)
|
||||
expect(page.find('[data-testid="js-file-deletion-line"]')).to have_text(diff_file.removed_lines)
|
||||
end
|
||||
|
||||
def render_component
|
||||
render_inline(described_class.new(diff_file: diff_file))
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::Viewers::Text::DiffHunkComponent, type: :component, feature_category: :code_review_workflow do
|
||||
let_it_be(:diff_file) { build(:diff_file) }
|
||||
|
||||
describe '#expand_buttons' do
|
||||
it { expect { create_component.expand_buttons }.to raise_error(NotImplementedError) }
|
||||
end
|
||||
|
||||
def create_component
|
||||
described_class.new(diff_file: diff_file, diff_hunk: { lines: [] })
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::Viewers::Text::ExpandLinesComponent, type: :component, feature_category: :code_review_workflow do
|
||||
it "renders expand up" do
|
||||
render_component(:up)
|
||||
expect(page).to have_selector('button svg use[href$="#expand-up"]')
|
||||
end
|
||||
|
||||
it "renders expand down" do
|
||||
render_component(:down)
|
||||
expect(page).to have_selector('button svg use[href$="#expand-down"]')
|
||||
end
|
||||
|
||||
it "renders expand both" do
|
||||
render_component(:both)
|
||||
expect(page).to have_selector('button svg use[href$="#expand"]')
|
||||
end
|
||||
|
||||
def render_component(direction)
|
||||
render_inline(described_class.new(direction: direction))
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::Viewers::Text::InlineHunkComponent, type: :component, feature_category: :code_review_workflow do
|
||||
let_it_be(:diff_file) { build(:diff_file) }
|
||||
let(:lines) { diff_file.diff_lines_with_match_tail }
|
||||
let(:hunk) do
|
||||
{
|
||||
header: lines.first,
|
||||
lines: lines.drop(1)
|
||||
}
|
||||
end
|
||||
|
||||
it "renders header" do
|
||||
render_component
|
||||
expect(page).to have_text(hunk[:header].text)
|
||||
end
|
||||
|
||||
it "renders lines" do
|
||||
render_component
|
||||
page_text = page.native.inner_html
|
||||
hunk[:lines].each do |line|
|
||||
text = line.rich_text || line.text
|
||||
expect(page_text).to include(text.gsub(/^[\s+-]/, ''))
|
||||
end
|
||||
end
|
||||
|
||||
it "renders line id" do
|
||||
old_line_id = diff_file.line_side_code(lines.second, :old)
|
||||
new_line_id = diff_file.line_side_code(lines.second, :new)
|
||||
render_component
|
||||
expect(page).to have_selector("##{old_line_id}")
|
||||
expect(page).to have_selector("##{new_line_id}")
|
||||
end
|
||||
|
||||
it "renders line link" do
|
||||
old_line_id = diff_file.line_side_code(lines.second, :old)
|
||||
new_line_id = diff_file.line_side_code(lines.second, :new)
|
||||
render_component
|
||||
expect(page).to have_selector("a[href='##{old_line_id}']")
|
||||
expect(page).to have_selector("a[href='##{new_line_id}']")
|
||||
end
|
||||
|
||||
it "renders legacy line id" do
|
||||
line_id = diff_file.line_code(lines.second)
|
||||
render_component
|
||||
expect(page).to have_selector("[data-legacy-id='#{line_id}']")
|
||||
end
|
||||
|
||||
it "renders expand up" do
|
||||
diff_hunk = {
|
||||
header: Gitlab::Diff::Line.new("", 'match', 1, 0, 0),
|
||||
lines: lines.drop(1)
|
||||
}
|
||||
render_component(diff_hunk)
|
||||
expect(page).to have_selector('button svg use[href$="#expand-up"]')
|
||||
end
|
||||
|
||||
it "renders expand down" do
|
||||
diff_hunk = {
|
||||
header: Gitlab::Diff::Line.new("", 'match', 100, 0, 0),
|
||||
lines: []
|
||||
}
|
||||
render_component(diff_hunk)
|
||||
expect(page).to have_selector('button svg use[href$="#expand-down"]')
|
||||
end
|
||||
|
||||
it "renders both expand up and down" do
|
||||
diff_hunk = {
|
||||
header: Gitlab::Diff::Line.new("", 'match', 1, 0, 0),
|
||||
lines: lines.drop(1),
|
||||
prev: { lines: [] }
|
||||
}
|
||||
render_component(diff_hunk)
|
||||
expect(page).to have_selector('button svg use[href$="#expand-up"]')
|
||||
expect(page).to have_selector('button svg use[href$="#expand-down"]')
|
||||
end
|
||||
|
||||
it "renders expand both" do
|
||||
last_prev_line = lines.first
|
||||
diff_hunk = {
|
||||
header: lines.first,
|
||||
lines: lines.drop(1),
|
||||
prev: {
|
||||
lines: [last_prev_line]
|
||||
}
|
||||
}
|
||||
allow(diff_hunk[:lines].first).to receive(:old_pos).and_return(5)
|
||||
allow(last_prev_line).to receive(:old_pos).and_return(2)
|
||||
render_component(diff_hunk)
|
||||
expect(page).to have_selector('button svg use[href$="#expand"]')
|
||||
end
|
||||
|
||||
def render_component(diff_hunk = hunk)
|
||||
render_inline(described_class.new(diff_file: diff_file, diff_hunk: diff_hunk))
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::Viewers::Text::InlineViewComponent, type: :component, feature_category: :code_review_workflow do
|
||||
let_it_be(:diff_file) { build(:diff_file) }
|
||||
let(:lines) { diff_file.diff_lines_with_match_tail }
|
||||
|
||||
it "renders inline lines" do
|
||||
render_component
|
||||
expect(page).to have_text(lines.first.rich_text)
|
||||
end
|
||||
|
||||
it "renders headings" do
|
||||
render_component
|
||||
page_text = page.native.inner_html
|
||||
headings = [
|
||||
'Original line number',
|
||||
'Diff line number',
|
||||
'Diff line'
|
||||
]
|
||||
headings.each do |heading|
|
||||
expect(page_text).to include(heading)
|
||||
end
|
||||
end
|
||||
|
||||
def render_component
|
||||
render_inline(described_class.new(diff_file: diff_file))
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::Viewers::Text::ParallelHunkComponent, type: :component, feature_category: :code_review_workflow do
|
||||
let_it_be(:diff_file) { build(:diff_file) }
|
||||
let(:lines) { diff_file.parallel_diff_lines_with_match_tail }
|
||||
let(:hunk) do
|
||||
{
|
||||
header: lines.first[:left],
|
||||
lines: lines.drop(1)
|
||||
}
|
||||
end
|
||||
|
||||
it "renders header" do
|
||||
render_component
|
||||
expect(page).to have_text(hunk[:header].text)
|
||||
end
|
||||
|
||||
it "renders lines" do
|
||||
render_component
|
||||
page_text = page.native.inner_html
|
||||
hunk[:lines].each do |line_side|
|
||||
[line_side[:left], line_side[:right]].compact.each do |line|
|
||||
text = line.rich_text || line.text
|
||||
expect(page_text).to include(text.gsub(/^[\s+-]/, ''))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "renders line id" do
|
||||
old_line_id = diff_file.line_side_code(lines.second[:left], :old)
|
||||
new_line_id = diff_file.line_side_code(lines.second[:right], :new)
|
||||
render_component
|
||||
expect(page).to have_selector("##{old_line_id}")
|
||||
expect(page).to have_selector("##{new_line_id}")
|
||||
end
|
||||
|
||||
it "renders line link" do
|
||||
old_line_id = diff_file.line_side_code(lines.second[:left], :old)
|
||||
new_line_id = diff_file.line_side_code(lines.second[:right], :new)
|
||||
render_component
|
||||
expect(page).to have_selector("a[href='##{old_line_id}']")
|
||||
expect(page).to have_selector("a[href='##{new_line_id}']")
|
||||
end
|
||||
|
||||
it "renders legacy line id" do
|
||||
line_id = diff_file.line_code(lines.second[:left])
|
||||
render_component
|
||||
expect(page).to have_selector("[data-legacy-id='#{line_id}']")
|
||||
end
|
||||
|
||||
it "renders expand up" do
|
||||
diff_hunk = {
|
||||
header: Gitlab::Diff::Line.new("", 'match', 1, 0, 0),
|
||||
lines: lines.drop(1)
|
||||
}
|
||||
render_component(diff_hunk)
|
||||
expect(page).to have_selector('button svg use[href$="#expand-up"]')
|
||||
end
|
||||
|
||||
it "renders expand down" do
|
||||
diff_hunk = {
|
||||
header: Gitlab::Diff::Line.new("", 'match', 100, 0, 0),
|
||||
lines: []
|
||||
}
|
||||
render_component(diff_hunk)
|
||||
expect(page).to have_selector('button svg use[href$="#expand-down"]')
|
||||
end
|
||||
|
||||
it "renders both expand up and down" do
|
||||
diff_hunk = {
|
||||
header: Gitlab::Diff::Line.new("", 'match', 1, 0, 0),
|
||||
lines: lines.drop(1),
|
||||
prev: { lines: [] }
|
||||
}
|
||||
render_component(diff_hunk)
|
||||
expect(page).to have_selector('button svg use[href$="#expand-up"]')
|
||||
expect(page).to have_selector('button svg use[href$="#expand-down"]')
|
||||
end
|
||||
|
||||
it "renders expand both" do
|
||||
last_prev_line = lines.first
|
||||
diff_hunk = {
|
||||
header: lines.first[:left],
|
||||
lines: lines.drop(1),
|
||||
prev: {
|
||||
lines: [last_prev_line]
|
||||
}
|
||||
}
|
||||
allow(diff_hunk[:lines].first[:left]).to receive(:old_pos).and_return(5)
|
||||
allow(last_prev_line[:left]).to receive(:old_pos).and_return(2)
|
||||
render_component(diff_hunk)
|
||||
expect(page).to have_selector('button svg use[href$="#expand"]')
|
||||
end
|
||||
|
||||
def render_component(diff_hunk = hunk)
|
||||
render_inline(described_class.new(diff_file: diff_file, diff_hunk: diff_hunk))
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::Viewers::Text::ParallelViewComponent, type: :component, feature_category: :code_review_workflow do
|
||||
let_it_be(:diff_file) { build(:diff_file) }
|
||||
let(:lines) { diff_file.parallel_diff_lines_with_match_tail }
|
||||
|
||||
it "renders parallel lines" do
|
||||
render_component
|
||||
expect(page).to have_text(lines.first[:left].rich_text)
|
||||
expect(page).to have_text(lines.first[:right].rich_text)
|
||||
end
|
||||
|
||||
it "renders headings" do
|
||||
render_component
|
||||
page_text = page.native.inner_html
|
||||
headings = [
|
||||
'Original line number',
|
||||
'Original line',
|
||||
'Diff line number',
|
||||
'Diff line'
|
||||
]
|
||||
headings.each do |heading|
|
||||
expect(page_text).to include(heading)
|
||||
end
|
||||
end
|
||||
|
||||
def render_component
|
||||
render_inline(described_class.new(diff_file: diff_file))
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::Viewers::Text::TextViewComponent, type: :component, feature_category: :code_review_workflow do
|
||||
let_it_be(:diff_file) { build(:diff_file) }
|
||||
|
||||
describe '#lines' do
|
||||
it { delegates_implementation_for { create_component.lines } }
|
||||
end
|
||||
|
||||
describe '#diff_line' do
|
||||
it { delegates_implementation_for { create_component.diff_line(diff_file.highlighted_diff_lines.first) } }
|
||||
end
|
||||
|
||||
describe '#hunk_view_component' do
|
||||
it { delegates_implementation_for { create_component.hunk_view_component } }
|
||||
end
|
||||
|
||||
describe '#column_titles' do
|
||||
it { delegates_implementation_for { create_component.column_titles } }
|
||||
end
|
||||
|
||||
def delegates_implementation_for
|
||||
expect { yield }.to raise_error(NotImplementedError)
|
||||
end
|
||||
|
||||
def create_component
|
||||
described_class.new(diff_file: diff_file)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::Viewers::ViewerComponent, type: :component, feature_category: :code_review_workflow do
|
||||
describe '#viewer_name' do
|
||||
it { expect { described_class.viewer_name }.to raise_error(NotImplementedError) }
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../support/helpers/repo_helpers'
|
||||
|
||||
FactoryBot.define do
|
||||
factory :diff_file, class: 'Gitlab::Diff::File' do
|
||||
repository { create(:project, :repository).repository }
|
||||
diff { repository.commit(RepoHelpers.sample_commit.id).raw_diffs.first }
|
||||
diff_refs { repository.commit(RepoHelpers.sample_commit.id).diff_refs }
|
||||
|
||||
initialize_with do
|
||||
new(
|
||||
attributes[:diff],
|
||||
diff_refs: attributes[:diff_refs],
|
||||
repository: attributes[:repository]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -694,7 +694,7 @@ RSpec.describe 'Copy as GFM', :js, feature_category: :team_planning do
|
|||
it 'copies as inline code' do
|
||||
verify(
|
||||
'[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]',
|
||||
'`raise RuntimeError, "System commands must be given as an array of strings"`',
|
||||
'` raise RuntimeError, "System commands must be given as an array of strings"`',
|
||||
target: '[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"]'
|
||||
)
|
||||
end
|
||||
|
|
@ -773,11 +773,11 @@ RSpec.describe 'Copy as GFM', :js, feature_category: :team_planning do
|
|||
wait_for_requests
|
||||
end
|
||||
|
||||
context 'selecting one word of text' do
|
||||
context 'selecting one line of text with a single word in it' do
|
||||
it 'copies as inline code' do
|
||||
verify(
|
||||
'.line[id="LC10"]',
|
||||
'`end`'
|
||||
'` end`'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -786,7 +786,7 @@ RSpec.describe 'Copy as GFM', :js, feature_category: :team_planning do
|
|||
it 'copies as inline code' do
|
||||
verify(
|
||||
'.line[id="LC9"]',
|
||||
'`raise RuntimeError, "System commands must be given as an array of strings"`'
|
||||
'` raise RuntimeError, "System commands must be given as an array of strings"`'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -825,7 +825,7 @@ RSpec.describe 'Copy as GFM', :js, feature_category: :team_planning do
|
|||
it 'copies as inline code' do
|
||||
verify(
|
||||
'.line[id="LC27"]',
|
||||
'`"bio": null,`'
|
||||
'` "bio": null,`'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import CommitRefs from '~/projects/commit_box/info/components/commit_refs.vue';
|
|||
|
||||
import {
|
||||
mockCommitReferencesResponse,
|
||||
mockOnlyBranchesResponse,
|
||||
mockContainingBranchesResponse,
|
||||
refsListPropsMock,
|
||||
} from '../mock_data';
|
||||
|
|
@ -54,7 +53,7 @@ describe('Commit references component', () => {
|
|||
await createComponent();
|
||||
});
|
||||
|
||||
it('renders component correcrly', () => {
|
||||
it('renders component correctly', () => {
|
||||
expect(findRefsLists()).toHaveLength(2);
|
||||
});
|
||||
|
||||
|
|
@ -77,11 +76,6 @@ describe('Commit references component', () => {
|
|||
expect(containingBranchesQueryHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not render list when there is no branches or tags', async () => {
|
||||
await createComponent(successQueryHandler(mockOnlyBranchesResponse));
|
||||
expect(findRefsLists()).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('with relative url', () => {
|
||||
beforeEach(async () => {
|
||||
gon.relative_url_root = '/gitlab';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GlCollapse, GlButton, GlBadge, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import { GlCollapse, GlButton, GlBadge, GlLoadingIcon, GlSkeletonLoader } from '@gitlab/ui';
|
||||
import RefsList from '~/projects/commit_box/info/components/refs_list.vue';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import {
|
||||
|
|
@ -23,11 +23,18 @@ describe('Commit references component', () => {
|
|||
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||
const findTippingRefs = () => wrapper.findAllComponents(GlBadge);
|
||||
const findContainingRefs = () => wrapper.findComponent(GlCollapse);
|
||||
const findEmptyMessage = () => wrapper.findByText('No related branches found');
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders a loading icon when loading', () => {
|
||||
createComponent({ isLoading: true });
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the namespace passed', () => {
|
||||
expect(findTitle().text()).toEqual(refsListPropsMock.namespace);
|
||||
});
|
||||
|
|
@ -65,11 +72,16 @@ describe('Commit references component', () => {
|
|||
expect(refBadge.attributes('href')).toBe(refUrl);
|
||||
});
|
||||
|
||||
it('does not reneder list of tipping branches or tags if there is no data', () => {
|
||||
it('does not render list of tipping branches or tags if there is no data', () => {
|
||||
createComponent({ tippingRefs: [] });
|
||||
expect(findTippingRefs().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders an empty message when there is no tipping and containing refs', () => {
|
||||
createComponent({ tippingRefs: [] });
|
||||
expect(findEmptyMessage().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders skeleton loader when isLoading prop has true value', () => {
|
||||
createComponent({ isLoading: true, containingRefs: [] });
|
||||
expect(findSkeletonLoader().exists()).toBe(true);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import Bold from '~/content_editor/extensions/bold';
|
|||
import Code from '~/content_editor/extensions/code';
|
||||
import { createTestEditor } from '../test_utils';
|
||||
|
||||
const CODE_HTML = `<p dir="auto" data-sourcepos="1:1-1:31"><code data-sourcepos="1:2-1:30"> code with leading spaces</code></p>`;
|
||||
|
||||
describe('content_editor/extensions/code', () => {
|
||||
let tiptapEditor;
|
||||
let doc;
|
||||
|
|
@ -31,6 +33,18 @@ describe('content_editor/extensions/code', () => {
|
|||
expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON());
|
||||
});
|
||||
|
||||
describe('when parsing HTML', () => {
|
||||
beforeEach(() => {
|
||||
tiptapEditor.commands.setContent(CODE_HTML);
|
||||
});
|
||||
|
||||
it('parses HTML correctly into an inline code block, preserving leading spaces', () => {
|
||||
expect(tiptapEditor.getJSON()).toEqual(
|
||||
doc(p(code(' code with leading spaces'))).toJSON(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shortcut: RightArrow', () => {
|
||||
it('exits the code block', () => {
|
||||
const initialDoc = doc(p('You can write ', code('java')));
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ import {
|
|||
const { paragraph, code, italic, bold, strike } = builders;
|
||||
|
||||
it.each`
|
||||
input | output
|
||||
${'code'} | ${'`code`'}
|
||||
${'code `with` backticks'} | ${'``code `with` backticks``'}
|
||||
${'this is `inline-code`'} | ${'`` this is `inline-code` ``'}
|
||||
${'`inline-code` in markdown'} | ${'`` `inline-code` in markdown ``'}
|
||||
${'```js'} | ${'`` ```js ``'}
|
||||
input | output
|
||||
${'code'} | ${'`code`'}
|
||||
${' code with leading spaces'} | ${'` code with leading spaces`'}
|
||||
${'code `with` backticks'} | ${'``code `with` backticks``'}
|
||||
${'this is `inline-code`'} | ${'`` this is `inline-code` ``'}
|
||||
${'`inline-code` in markdown'} | ${'`` `inline-code` in markdown ``'}
|
||||
${'```js'} | ${'`` ```js ``'}
|
||||
`('correctly serializes inline code ("$input")', ({ input, output }) => {
|
||||
expect(serialize(paragraph(code(input)))).toBe(output);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ exports[`Merge request dashboard merge request component renders template 1`] =
|
|||
>
|
||||
<div
|
||||
aria-label="5 comments, 100 file additions, 50 file deletions"
|
||||
class="gl-flex gl-justify-end"
|
||||
class="gl-flex gl-gap-3 gl-justify-end"
|
||||
>
|
||||
<div
|
||||
class="gl-whitespace-nowrap"
|
||||
|
|
@ -75,7 +75,7 @@ exports[`Merge request dashboard merge request component renders template 1`] =
|
|||
5
|
||||
</div>
|
||||
<div
|
||||
class="gl-ml-5 gl-whitespace-nowrap"
|
||||
class="gl-whitespace-nowrap"
|
||||
>
|
||||
<gl-icon-stub
|
||||
name="doc-code"
|
||||
|
|
@ -87,7 +87,7 @@ exports[`Merge request dashboard merge request component renders template 1`] =
|
|||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="gl-flex gl-font-bold gl-items-center gl-ml-3 gl-text-green-600"
|
||||
class="gl-flex gl-font-bold gl-items-center gl-text-green-600"
|
||||
>
|
||||
<span>
|
||||
+
|
||||
|
|
@ -97,7 +97,7 @@ exports[`Merge request dashboard merge request component renders template 1`] =
|
|||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="gl-flex gl-font-bold gl-items-center gl-ml-3 gl-text-red-500"
|
||||
class="gl-flex gl-font-bold gl-items-center gl-text-red-500"
|
||||
>
|
||||
<span>
|
||||
−
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
|
||||
describe('ExpirationToggle', () => {
|
||||
let wrapper;
|
||||
const value = 'foo';
|
||||
const value = new Date().toISOString();
|
||||
|
||||
const findInput = () => wrapper.findComponent(GlFormInput);
|
||||
const findFormGroup = () => wrapper.findComponent(GlFormGroup);
|
||||
|
|
@ -44,7 +44,7 @@ describe('ExpirationToggle', () => {
|
|||
describe('formattedValue', () => {
|
||||
it.each`
|
||||
valueProp | enabled | expected
|
||||
${value} | ${true} | ${value}
|
||||
${value} | ${true} | ${'July 6, 2020 at 12:00:00 AM GMT'}
|
||||
${value} | ${false} | ${NOT_SCHEDULED_POLICY_TEXT}
|
||||
${undefined} | ${false} | ${NOT_SCHEDULED_POLICY_TEXT}
|
||||
${undefined} | ${true} | ${NOT_SCHEDULED_POLICY_TEXT}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::BackgroundMigration::RemoveNamespaceFromOsTypeSbomComponents, feature_category: :software_composition_analysis do
|
||||
RSpec.describe Gitlab::BackgroundMigration::RemoveNamespaceFromOsTypeSbomComponents, schema: 20240909204952, feature_category: :software_composition_analysis do
|
||||
let(:components) { table(:sbom_components) }
|
||||
let(:expected) do
|
||||
(0...os_prefix_to_purl_type_mapping.size).map { |n| "package-#{n}" }
|
||||
|
|
|
|||
|
|
@ -1302,4 +1302,46 @@ RSpec.describe Gitlab::Diff::File do
|
|||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#line_side_code' do
|
||||
let(:line) { instance_double(Gitlab::Diff::Line, type: 'old', old_pos: 4, new_pos: 4, added?: false, removed?: true, text: 'First Hunk Removed 1', meta?: false) }
|
||||
|
||||
it 'returns the correct left side ID' do
|
||||
expect(diff_file.line_side_code(line, :old)).to eq("line_#{diff_file.file_hash}_L#{line.old_pos}")
|
||||
end
|
||||
|
||||
it 'returns the correct right side ID' do
|
||||
expect(diff_file.line_side_code(line, :new)).to eq("line_#{diff_file.file_hash}_R#{line.new_pos}")
|
||||
end
|
||||
end
|
||||
|
||||
describe '#text_diff' do
|
||||
subject(:text_diff) { diff_file.text_diff? }
|
||||
|
||||
it 'returns true for text diffs' do
|
||||
expect(text_diff).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false for unchanged files' do
|
||||
allow(diff_file).to receive(:modified_file?).and_return(false)
|
||||
expect(text_diff).to eq(false)
|
||||
end
|
||||
|
||||
it 'returns false for non text files' do
|
||||
allow(diff_file).to receive(:text?).and_return(false)
|
||||
expect(text_diff).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diff_lines_with_match_tail' do
|
||||
subject(:lines) { diff_file.diff_lines_with_match_tail }
|
||||
|
||||
it { expect(lines.last.type).to eq('match') }
|
||||
end
|
||||
|
||||
describe '#parallel_diff_lines_with_match_tail' do
|
||||
subject(:lines) { diff_file.parallel_diff_lines_with_match_tail }
|
||||
|
||||
it { expect(lines.last[:left].type).to eq('match') }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -60,5 +60,14 @@ RSpec.describe Gitlab::Diff::ParallelDiff do
|
|||
|
||||
expect(subject.parallelize).to eq(expected)
|
||||
end
|
||||
|
||||
it 'can accept diff lines' do
|
||||
diff_lines = [Gitlab::Diff::Line.new("", 'match', nil, 1, 1)]
|
||||
expect(subject.parallelize(diff_lines)).to eq([
|
||||
{
|
||||
left: diff_lines[0], right: diff_lines[0]
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,195 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe ::Ci::CollectPipelineAnalyticsService, :click_house, :enable_admin_mode,
|
||||
feature_category: :fleet_visibility do
|
||||
include ClickHouseHelpers
|
||||
|
||||
let_it_be(:project1) { create(:project).tap(&:reload) } # reload required to calculate traversal path
|
||||
let_it_be(:project2) { create(:project).tap(&:reload) }
|
||||
let_it_be(:current_user) { create(:user, reporter_of: [project1, project2]) }
|
||||
|
||||
let_it_be(:starting_time) { Time.utc(2023) }
|
||||
let_it_be(:ending_time) { 1.week.after(Time.utc(2023)) }
|
||||
|
||||
let(:project) { project1 }
|
||||
let(:status_groups) { [:all] }
|
||||
let(:from_time) { starting_time }
|
||||
let(:to_time) { ending_time }
|
||||
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
current_user: current_user,
|
||||
project: project,
|
||||
from_time: from_time,
|
||||
to_time: to_time,
|
||||
status_groups: status_groups)
|
||||
end
|
||||
|
||||
let(:pipelines) do
|
||||
[
|
||||
create_pipeline(project1, :running, 35.minutes.before(ending_time), 30.minutes),
|
||||
create_pipeline(project1, :success, 1.day.before(ending_time), 30.minutes),
|
||||
create_pipeline(project1, :canceled, 1.hour.before(ending_time), 1.minute),
|
||||
create_pipeline(project1, :failed, 5.days.before(ending_time), 2.hours),
|
||||
create_pipeline(project1, :failed, 1.week.before(ending_time), 45.minutes),
|
||||
create_pipeline(project1, :skipped, 5.days.before(ending_time), 1.second),
|
||||
create_pipeline(project1, :skipped, 1.second.before(starting_time), 45.minutes),
|
||||
create_pipeline(project1, :success, ending_time, 30.minutes)
|
||||
]
|
||||
end
|
||||
|
||||
subject(:result) { service.execute }
|
||||
|
||||
before do
|
||||
insert_ci_pipelines_to_click_house(pipelines)
|
||||
end
|
||||
|
||||
context 'when ClickHouse database is not configured' do
|
||||
before do
|
||||
allow(::Gitlab::ClickHouse).to receive(:configured?).and_return(false)
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
expect(result.error?).to eq(true)
|
||||
expect(result.errors).to contain_exactly('ClickHouse database is not configured')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'returns Not allowed error' do
|
||||
it 'returns error' do
|
||||
expect(result.error?).to eq(true)
|
||||
expect(result.errors).to contain_exactly('Not allowed')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a service returning aggregate analytics' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:status_groups, :expected_aggregate) do
|
||||
%i[all] | { all: 6 }
|
||||
%i[all success] | { all: 6, success: 1 }
|
||||
%i[success other] | { success: 1, other: 2 }
|
||||
%i[failed] | { failed: 2 }
|
||||
end
|
||||
|
||||
with_them do
|
||||
it 'returns aggregate analytics' do
|
||||
expect(result.success?).to eq(true)
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.payload[:aggregate]).to eq(expected_aggregate)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dates are not specified' do
|
||||
let(:from_time) { nil }
|
||||
let(:to_time) { nil }
|
||||
|
||||
context 'and there are pipelines in the last week', time_travel_to: '2023-01-08' do
|
||||
it 'returns aggregate analytics from last week' do
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.success?).to eq(true)
|
||||
expect(result.payload[:aggregate]).to eq({ all: 6 })
|
||||
end
|
||||
end
|
||||
|
||||
context 'and there are no pipelines in the last week', time_travel_to: '2023-01-15 00:00:01' do
|
||||
it 'returns empty aggregate analytics' do
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.success?).to eq(true)
|
||||
expect(result.payload[:aggregate]).to eq({ all: 0 })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when requesting statistics starting one second before beginning of week' do
|
||||
let(:from_time) { 1.second.before(starting_time) }
|
||||
|
||||
it 'does not include job starting 1 second before start of week' do
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.success?).to eq(true)
|
||||
expect(result.payload[:aggregate]).to eq({ all: 6 })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when requesting statistics starting one hour before beginning of week' do
|
||||
let(:from_time) { 1.hour.before(starting_time) }
|
||||
|
||||
it 'includes job starting 1 second before start of week' do
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.success?).to eq(true)
|
||||
expect(result.payload[:aggregate]).to eq({ all: 7 })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when requesting hourly statistics that span more than one week' do
|
||||
let(:from_time) { (1.hour + 1.second).before(starting_time) }
|
||||
|
||||
it 'returns an error' do
|
||||
expect(result.errors).to contain_exactly(
|
||||
"Maximum of #{described_class::TIME_BUCKETS_LIMIT} 1-hour intervals can be requested")
|
||||
expect(result.error?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a different project is specified' do
|
||||
let(:project) { project2 }
|
||||
let(:status_groups) { %i[all success failed] }
|
||||
|
||||
before do
|
||||
insert_ci_pipelines_to_click_house([
|
||||
create_pipeline(project2, :failed, 1.week.before(ending_time), 45.minutes)
|
||||
])
|
||||
end
|
||||
|
||||
it 'returns aggregate analytics for specified project only' do
|
||||
expect(result.success?).to eq(true)
|
||||
expect(result.errors).to eq([])
|
||||
expect(result.payload[:aggregate]).to eq({ all: 1, success: 0, failed: 1 })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'a service returning aggregate analytics'
|
||||
|
||||
context 'when user is nil' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
include_examples 'returns Not allowed error'
|
||||
end
|
||||
|
||||
context 'when project has analytics disabled' do
|
||||
let_it_be(:project) { create(:project, :analytics_disabled) }
|
||||
|
||||
include_examples 'returns Not allowed error'
|
||||
end
|
||||
|
||||
context 'when project is not specified' do
|
||||
let(:project) { nil }
|
||||
|
||||
it 'returns error' do
|
||||
expect(result.error?).to eq(true)
|
||||
expect(result.errors).to contain_exactly('Project must be specified')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is an admin' do
|
||||
let(:current_user) { create(:admin) }
|
||||
|
||||
it_behaves_like 'a service returning aggregate analytics'
|
||||
end
|
||||
|
||||
context 'when user is a guest' do
|
||||
let_it_be(:current_user) { create(:user, guest_of: project1) }
|
||||
|
||||
include_examples 'returns Not allowed error'
|
||||
end
|
||||
|
||||
def create_pipeline(project, status, started_at, duration)
|
||||
build_stubbed(:ci_pipeline, status,
|
||||
project: project,
|
||||
created_at: 1.second.before(started_at), started_at: started_at, finished_at: duration.after(started_at),
|
||||
duration: duration)
|
||||
end
|
||||
end
|
||||
|
|
@ -41,6 +41,18 @@ module ClickHouseHelpers
|
|||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
|
||||
def insert_ci_pipelines_to_click_house(pipelines)
|
||||
result = clickhouse_fixture(:ci_finished_pipelines, pipelines.map do |pipeline|
|
||||
pipeline.slice(
|
||||
%i[id duration status source ref committed_at created_at started_at finished_at]).symbolize_keys
|
||||
.merge(
|
||||
path: pipeline.project&.project_namespace&.traversal_path || '0/'
|
||||
)
|
||||
end)
|
||||
|
||||
expect(result).to eq(true)
|
||||
end
|
||||
|
||||
def self.default_timezone
|
||||
ActiveRecord.default_timezone
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1876,7 +1876,6 @@
|
|||
- './ee/spec/requests/api/internal/app_sec/dast/site_validations_spec.rb'
|
||||
- './ee/spec/requests/api/internal/base_spec.rb'
|
||||
- './ee/spec/requests/api/internal/kubernetes_spec.rb'
|
||||
- './ee/spec/requests/api/internal/upcoming_reconciliations_spec.rb'
|
||||
- './ee/spec/requests/api/invitations_spec.rb'
|
||||
- './ee/spec/requests/api/issue_links_spec.rb'
|
||||
- './ee/spec/requests/api/issues_spec.rb'
|
||||
|
|
@ -2462,7 +2461,6 @@
|
|||
- './ee/spec/services/todos/allowed_target_filter_service_spec.rb'
|
||||
- './ee/spec/services/todos/destroy/confidential_epic_service_spec.rb'
|
||||
- './ee/spec/services/todo_service_spec.rb'
|
||||
- './ee/spec/services/upcoming_reconciliations/update_service_spec.rb'
|
||||
- './ee/spec/services/user_permissions/export_service_spec.rb'
|
||||
- './ee/spec/services/users/abuse/git_abuse/namespace_throttle_service_spec.rb'
|
||||
- './ee/spec/services/users/abuse/namespace_bans/create_service_spec.rb'
|
||||
|
|
|
|||
|
|
@ -22,6 +22,51 @@ RSpec.shared_examples_for 'a ci_finished_pipelines aggregation model' do |table_
|
|||
end
|
||||
end
|
||||
|
||||
describe '#within_dates' do
|
||||
let(:from_time) { 1.hour.ago }
|
||||
let(:to_time) { Time.current }
|
||||
|
||||
subject(:result_sql) { instance.within_dates(from_time, to_time).to_sql }
|
||||
|
||||
it 'builds the correct SQL' do
|
||||
expected_sql = <<~SQL.lines(chomp: true).join(' ')
|
||||
SELECT * FROM "#{table_name}"
|
||||
WHERE "#{table_name}"."started_at_bucket" >= toDateTime64('#{from_time.utc.strftime('%Y-%m-%d %H:%M:%S')}', 6, 'UTC')
|
||||
AND "#{table_name}"."started_at_bucket" < toDateTime64('#{to_time.utc.strftime('%Y-%m-%d %H:%M:%S')}', 6, 'UTC')
|
||||
SQL
|
||||
|
||||
expect(result_sql.strip).to eq(expected_sql.strip)
|
||||
end
|
||||
|
||||
context 'when only from_date is passed' do
|
||||
let(:from_time) { 1.hour.ago }
|
||||
let(:to_time) { nil }
|
||||
|
||||
it 'builds the correct SQL' do
|
||||
expected_sql = <<~SQL.lines(chomp: true).join(' ')
|
||||
SELECT * FROM "#{table_name}"
|
||||
WHERE "#{table_name}"."started_at_bucket" >= toDateTime64('#{from_time.utc.strftime('%Y-%m-%d %H:%M:%S')}', 6, 'UTC')
|
||||
SQL
|
||||
|
||||
expect(result_sql.strip).to eq(expected_sql.strip)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when only to_date is passed' do
|
||||
let(:from_time) { nil }
|
||||
let(:to_time) { Time.current }
|
||||
|
||||
it 'builds the correct SQL' do
|
||||
expected_sql = <<~SQL.lines(chomp: true).join(' ')
|
||||
SELECT * FROM "#{table_name}"
|
||||
WHERE "#{table_name}"."started_at_bucket" < toDateTime64('#{to_time.utc.strftime('%Y-%m-%d %H:%M:%S')}', 6, 'UTC')
|
||||
SQL
|
||||
|
||||
expect(result_sql.strip).to eq(expected_sql.strip)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#by_status' do
|
||||
subject(:result_sql) { instance.by_status(%i[failed success]).to_sql }
|
||||
|
||||
|
|
@ -98,13 +143,25 @@ RSpec.shared_examples_for 'a ci_finished_pipelines aggregation model' do |table_
|
|||
end
|
||||
|
||||
it 'builds the correct SQL with chained methods' do
|
||||
from_time = 1.hour.ago
|
||||
to_time = Time.current
|
||||
|
||||
expected_sql = <<~SQL.lines(chomp: true).join(' ')
|
||||
SELECT "#{table_name}"."status" FROM "#{table_name}"
|
||||
WHERE "#{table_name}"."path" = '#{path}'
|
||||
AND "#{table_name}"."started_at_bucket" >= toDateTime64('#{from_time.utc.strftime('%Y-%m-%d %H:%M:%S')}', 6, 'UTC')
|
||||
AND "#{table_name}"."started_at_bucket" < toDateTime64('#{to_time.utc.strftime('%Y-%m-%d %H:%M:%S')}', 6, 'UTC')
|
||||
AND "#{table_name}"."status" IN ('failed', 'success')
|
||||
GROUP BY "#{table_name}"."status"
|
||||
SQL
|
||||
|
||||
result_sql = instance
|
||||
.for_project(project)
|
||||
.select(:status)
|
||||
.within_dates(from_time, to_time)
|
||||
.by_status(%i[failed success])
|
||||
.group_by_status.to_sql
|
||||
|
||||
expect(result_sql.strip).to eq(expected_sql.strip)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -724,9 +724,11 @@ RSpec.describe 'gitlab:backup namespace rake tasks', :reestablished_active_recor
|
|||
end
|
||||
|
||||
def expect_logger_to_receive_messages(messages)
|
||||
expect_any_instance_of(Gitlab::BackupLogger) do |logger|
|
||||
messages.each do |message|
|
||||
allow(logger).to receive(:info).with(message).ordered
|
||||
[Gitlab::BackupLogger, Gitlab::Backup::JsonLogger].each do |log_class|
|
||||
expect_any_instance_of(log_class) do |logger|
|
||||
messages.each do |message|
|
||||
allow(logger).to receive(:info).with(message).ordered
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -122,6 +122,26 @@ RSpec.describe RemoveExpiredMembersWorker, feature_category: :system_access do
|
|||
'meta.user' => expired_group_member.user.username
|
||||
)
|
||||
end
|
||||
|
||||
context 'when the user has a direct membership in a subproject' do
|
||||
let_it_be(:subproject) { create(:project, group: expired_group_member.group) }
|
||||
let_it_be(:non_expired_project_membership) { create(:project_member, user: expired_group_member.user, access_level: ProjectMember::MAINTAINER, project: subproject) }
|
||||
|
||||
it 'does not expire the membership in the subgroup' do
|
||||
worker.perform
|
||||
expect(non_expired_project_membership.reload).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user has a direct membership in a subgroup' do
|
||||
let_it_be(:subgroup) { create(:group, parent: expired_group_member.group) }
|
||||
let_it_be(:non_expired_group_membership) { create(:group_member, user: expired_group_member.user, access_level: GroupMember::MAINTAINER, group: subgroup) }
|
||||
|
||||
it 'does not expire the membership in the subgroup' do
|
||||
worker.perform
|
||||
expect(non_expired_group_membership.reload).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the last group owner expires' do
|
||||
|
|
|
|||
|
|
@ -24,12 +24,7 @@ cmd/gitlab-zip-metadata/limit/reader.go:37:1: exported: exported function NewLim
|
|||
cmd/gitlab-zip-metadata/main.go:1:1: package-comments: should have a package comment (revive)
|
||||
cmd/gitlab-zip-metadata/main.go:17:5: exported: exported var Version should have comment or be unexported (revive)
|
||||
cmd/gitlab-zip-metadata/main.go:66:9: superfluous-else: if block ends with call to os.Exit function, so drop this else and outdent its block (revive)
|
||||
internal/api/api.go:146:2: var-naming: don't use ALL_CAPS in Go names; use CamelCase (revive)
|
||||
internal/api/api.go:149:2: var-naming: don't use ALL_CAPS in Go names; use CamelCase (revive)
|
||||
internal/api/api.go:153:2: var-naming: don't use ALL_CAPS in Go names; use CamelCase (revive)
|
||||
internal/api/block_test.go:61:34: response body must be closed (bodyclose)
|
||||
internal/api/channel_settings.go:57:28: G402: TLS MinVersion too low. (gosec)
|
||||
internal/builds/register.go:120: Function 'RegisterHandler' is too long (66 > 60) (funlen)
|
||||
internal/channel/channel.go:128:31: response body must be closed (bodyclose)
|
||||
internal/config/config.go:1:1: package-comments: should have a package comment (revive)
|
||||
internal/config/config.go:27:7: exported: exported const Megabyte should have comment or be unexported (revive)
|
||||
|
|
|
|||
|
|
@ -143,14 +143,14 @@ type RemoteObject struct {
|
|||
type Response struct {
|
||||
// GL_ID is an environment variable used by gitlab-shell hooks during 'git
|
||||
// push' and 'git pull'
|
||||
GL_ID string
|
||||
GL_ID string // nolint:stylecheck,revive // used as env variable
|
||||
|
||||
// GL_USERNAME holds gitlab username of the user who is taking the action causing hooks to be invoked
|
||||
GL_USERNAME string
|
||||
GL_USERNAME string // nolint:stylecheck,revive // used as env variable
|
||||
|
||||
// GL_REPOSITORY is an environment variable used by gitlab-shell hooks during
|
||||
// 'git push' and 'git pull'
|
||||
GL_REPOSITORY string
|
||||
GL_REPOSITORY string // nolint:stylecheck,revive // used as env variable
|
||||
|
||||
// GitConfigOptions holds the custom options that we want to pass to the git command
|
||||
GitConfigOptions []string
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ func TestBlocker(t *testing.T) {
|
|||
func TestBlockerFlushable(t *testing.T) {
|
||||
rw := httptest.NewRecorder()
|
||||
b := blocker{rw: rw}
|
||||
rc := http.NewResponseController(&b)
|
||||
rc := http.NewResponseController(&b) //nolint:bodyclose
|
||||
|
||||
err := rc.Flush()
|
||||
require.NoError(t, err, "the underlying response writer is not flushable")
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ func watchForRunnerChange(ctx context.Context, watchHandler WatchKeyHandler, tok
|
|||
return watchHandler(ctx, runnerBuildQueue+token, lastUpdate, duration)
|
||||
}
|
||||
|
||||
// RegisterHandler with key watch logic if polling is enabled.
|
||||
func RegisterHandler(h http.Handler, watchHandler WatchKeyHandler, pollingDuration time.Duration) http.Handler {
|
||||
if pollingDuration == 0 {
|
||||
return h
|
||||
|
|
@ -135,16 +136,8 @@ func RegisterHandler(h http.Handler, watchHandler WatchKeyHandler, pollingDurati
|
|||
|
||||
newRequest := cloneRequestWithNewBody(r, requestBody)
|
||||
|
||||
runnerRequest, err := readRunnerRequest(r, requestBody)
|
||||
if err != nil {
|
||||
registerHandlerBodyParseErrors.Inc()
|
||||
proxyRegisterRequest(h, w, newRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if runnerRequest.Token == "" || runnerRequest.LastUpdate == "" {
|
||||
registerHandlerMissingValues.Inc()
|
||||
proxyRegisterRequest(h, w, newRequest)
|
||||
runnerRequest, shouldReturn := getRunnerRequest(r, requestBody, h, w, newRequest)
|
||||
if shouldReturn {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -186,6 +179,22 @@ func RegisterHandler(h http.Handler, watchHandler WatchKeyHandler, pollingDurati
|
|||
})
|
||||
}
|
||||
|
||||
func getRunnerRequest(r *http.Request, requestBody []byte, h http.Handler, w http.ResponseWriter, newRequest *http.Request) (*runnerRequest, bool) {
|
||||
runnerRequest, err := readRunnerRequest(r, requestBody)
|
||||
if err != nil {
|
||||
registerHandlerBodyParseErrors.Inc()
|
||||
proxyRegisterRequest(h, w, newRequest)
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if runnerRequest.Token == "" || runnerRequest.LastUpdate == "" {
|
||||
registerHandlerMissingValues.Inc()
|
||||
proxyRegisterRequest(h, w, newRequest)
|
||||
return nil, true
|
||||
}
|
||||
return runnerRequest, false
|
||||
}
|
||||
|
||||
func cloneRequestWithNewBody(r *http.Request, body []byte) *http.Request {
|
||||
newReq := r.Clone(r.Context())
|
||||
newReq.Body = io.NopCloser(bytes.NewReader(body))
|
||||
|
|
|
|||
Loading…
Reference in New Issue