Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-02-22 15:18:06 +00:00
parent 59f160b0cf
commit 95b9a603c3
50 changed files with 693 additions and 121 deletions

View File

@ -105,6 +105,8 @@ overrides:
message: 'Avoid using "setData" on VTU wrapper'
- selector: MemberExpression[object.type!='ThisExpression'][property.type='Identifier'][property.name='$nextTick']
message: 'Using $nextTick from a component instance is discouraged. Import nextTick directly from the Vue package.'
- selector: Identifier[name='setImmediate']
message: 'Prefer explicit waitForPromises (or equivalent), or jest.runAllTimers (or equivalent) to vague setImmediate calls.'
- files:
- 'config/**/*'
- 'scripts/**/*'

View File

@ -4,6 +4,3 @@ GraphQL/OrderedArguments:
- app/graphql/resolvers/base_issues_resolver.rb
- app/graphql/resolvers/design_management/designs_resolver.rb
- app/graphql/resolvers/design_management/version/design_at_version_resolver.rb
- app/graphql/types/notes/diff_image_position_input_type.rb
- app/graphql/types/notes/diff_position_base_input_type.rb
- app/graphql/types/notes/diff_position_input_type.rb

View File

@ -1 +1 @@
c1cabced28d02d667a37122860adde10068d28e2
51cf676fc530f242eb893c7b630ef309de116e35

View File

@ -534,4 +534,4 @@ gem 'ipaddress', '~> 0.8.3'
gem 'parslet', '~> 1.8'
gem 'ipynbdiff', '0.3.8'
gem 'ipynbdiff', '0.4.2'

View File

@ -648,9 +648,9 @@ GEM
invisible_captcha (1.1.0)
rails (>= 4.2)
ipaddress (0.8.3)
ipynbdiff (0.3.8)
diffy (= 3.3.0)
json (= 2.5.1)
ipynbdiff (0.4.2)
diffy (~> 3.3)
json (~> 2.5, >= 2.5.1)
jaeger-client (1.1.0)
opentracing (~> 0.3)
thrift
@ -1507,7 +1507,7 @@ DEPENDENCIES
icalendar
invisible_captcha (~> 1.1.0)
ipaddress (~> 0.8.3)
ipynbdiff (= 0.3.8)
ipynbdiff (= 0.4.2)
jira-ruby (~> 2.1.4)
js_regex (~> 3.7)
json (~> 2.5.1)

View File

@ -1,4 +1,5 @@
import $ from 'jquery';
import { merge } from 'lodash';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
@ -41,6 +42,8 @@ export default class Diff {
}
this.openAnchoredDiff();
this.prepareRenderedDiff();
}
handleClickUnfold(e) {
@ -150,4 +153,43 @@ export default class Diff {
.addClass('hll');
}
}
prepareRenderedDiff() {
const $elements = $('[data-diff-toggle-entity]');
if ($elements.length === 0) return;
const diff = this;
const elements = $elements.toArray().map(this.formatElementToObject).reduce(merge);
Object.values(elements).forEach((e) => {
e.toShowBtn.onclick = () => diff.showOneHideAnother('rendered', e); // eslint-disable-line no-param-reassign
e.toHideBtn.onclick = () => diff.showOneHideAnother('raw', e); // eslint-disable-line no-param-reassign
diff.showOneHideAnother('raw', e);
});
}
formatElementToObject = (element) => {
const key = element.attributes['data-file-hash'].value;
const name = element.attributes['data-diff-toggle-entity'].value;
return { [key]: { [name]: element } };
};
showOneHideAnother = (mode, elements) => {
let { toShowBtn, toHideBtn, toShow, toHide } = elements;
if (mode === 'raw') {
[toShowBtn, toHideBtn] = [toHideBtn, toShowBtn];
[toShow, toHide] = [toHide, toShow];
}
toShowBtn.classList.add('selected');
toHideBtn.classList.remove('selected');
toHide.classList.add('hidden');
toShow.classList.remove('hidden');
};
}

View File

@ -164,6 +164,7 @@ class Projects::CommitController < Projects::ApplicationController
opts = diff_options
opts[:ignore_whitespace_change] = true if params[:format] == 'diff'
opts[:use_extra_viewer_as_main] = false
@diffs = commit.diffs(opts)
@notes_count = commit.notes.count

View File

@ -38,6 +38,8 @@ module Resolvers
.validate(content, dry_run: dry_run)
response(result).merge(merged_yaml: result.merged_yaml)
rescue GRPC::InvalidArgument => error
Gitlab::ErrorTracking.track_and_raise_exception(error, sha: sha)
end
private

View File

@ -5,14 +5,14 @@ module Types
class DiffImagePositionInputType < DiffPositionBaseInputType
graphql_name 'DiffImagePositionInput'
argument :height, GraphQL::Types::Int, required: true,
description: copy_field_description(Types::Notes::DiffPositionType, :height)
argument :width, GraphQL::Types::Int, required: true,
description: copy_field_description(Types::Notes::DiffPositionType, :width)
argument :x, GraphQL::Types::Int, required: true,
description: copy_field_description(Types::Notes::DiffPositionType, :x)
argument :y, GraphQL::Types::Int, required: true,
description: copy_field_description(Types::Notes::DiffPositionType, :y)
argument :width, GraphQL::Types::Int, required: true,
description: copy_field_description(Types::Notes::DiffPositionType, :width)
argument :height, GraphQL::Types::Int, required: true,
description: copy_field_description(Types::Notes::DiffPositionType, :height)
end
end
end

View File

@ -3,10 +3,10 @@
module Types
module Notes
class DiffPositionBaseInputType < BaseInputObject
argument :base_sha, GraphQL::Types::String, required: false,
description: copy_field_description(Types::DiffRefsType, :base_sha)
argument :head_sha, GraphQL::Types::String, required: true,
description: copy_field_description(Types::DiffRefsType, :head_sha)
argument :base_sha, GraphQL::Types::String, required: false,
description: copy_field_description(Types::DiffRefsType, :base_sha)
argument :start_sha, GraphQL::Types::String, required: true,
description: copy_field_description(Types::DiffRefsType, :start_sha)

View File

@ -5,10 +5,10 @@ module Types
class DiffPositionInputType < DiffPositionBaseInputType
graphql_name 'DiffPositionInput'
argument :old_line, GraphQL::Types::Int, required: false,
description: copy_field_description(Types::Notes::DiffPositionType, :old_line)
argument :new_line, GraphQL::Types::Int, required: false,
description: copy_field_description(Types::Notes::DiffPositionType, :new_line)
argument :old_line, GraphQL::Types::Int, required: false,
description: copy_field_description(Types::Notes::DiffPositionType, :old_line)
end
end
end

View File

@ -182,6 +182,19 @@ module CommitsHelper
project_commit_path(project, DEFAULT_SHA).sub("/#{DEFAULT_SHA}", '/$COMMIT_SHA')
end
def diff_mode_swap_button(mode, file_hash)
icon = mode == 'raw' ? 'doc-code' : 'doc-text'
entity = mode == 'raw' ? 'toHideBtn' : 'toShowBtn'
title = "Display #{mode} diff"
link_to("##{mode}-diff-#{file_hash}",
class: "btn gl-button btn-default btn-file-option has-tooltip btn-show-#{mode}-diff",
title: title,
data: { file_hash: file_hash, diff_toggle_entity: entity }) do
sprite_icon(icon)
end
end
protected
# Private: Returns a link to a person. If the person has a matching user and

View File

@ -28,7 +28,7 @@ module DiffHelper
end
def diff_options
options = { ignore_whitespace_change: hide_whitespace?, expanded: diffs_expanded? }
options = { ignore_whitespace_change: hide_whitespace?, expanded: diffs_expanded?, use_extra_viewer_as_main: true }
if action_name == 'diff_for_path'
options[:expanded] = true
@ -74,7 +74,7 @@ module DiffHelper
end
def diff_link_number(line_type, match, text)
line_type == match ? " " : text
line_type == match || text == 0 ? " " : text
end
def parallel_diff_discussions(left, right, diff_file)

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Blobs
class Notebook < ::Blob
attr_reader :data
def initialize(blob, data)
super(blob.__getobj__, blob.container)
@data = data
end
end
end

View File

@ -145,7 +145,7 @@ class DiffNote < Note
end
def fetch_diff_file
return note_diff_file.raw_diff_file if note_diff_file
return note_diff_file.raw_diff_file if note_diff_file && !note_diff_file.raw_diff_file.has_renderable?
if created_at_diff?(noteable.diff_refs)
# We're able to use the already persisted diffs (Postgres) if we're

View File

@ -1,5 +1,4 @@
# frozen_string_literal: true
require 'ipynbdiff'
class BlobPresenter < Gitlab::View::Presenter::Delegated
include ApplicationHelper

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Blobs
class NotebookPresenter < ::BlobPresenter
def gitattr_language
'md'
end
end
end

View File

@ -1,3 +1,12 @@
- diff_file = local_assigns.fetch(:diff_file, nil)
- file_hash = hexdigest(diff_file.file_path)
.diff-content
= render 'projects/diffs/viewer', viewer: diff_file.viewer
- if diff_file.has_renderable?
%div{ id: "#raw-diff-#{file_hash}", data: { file_hash: file_hash, diff_toggle_entity: 'toHide' } }
= render 'projects/diffs/viewer', viewer: diff_file.viewer
%div{ id: "#rendered-diff-#{file_hash}", data: { file_hash: file_hash, diff_toggle_entity: 'toShow' } }
= render 'projects/diffs/viewer', viewer: diff_file.rendered.viewer
- else
= render 'projects/diffs/viewer', viewer: diff_file.viewer

View File

@ -25,6 +25,11 @@
= edit_blob_button(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path,
blob: diff_file.blob, link_opts: link_opts)
- if diff_file.has_renderable?
.btn-group.gl-ml-3
= diff_mode_swap_button('raw', file_hash)
= diff_mode_swap_button('rendered', file_hash)
- if image_diff && image_replaced
= view_file_button(diff_file.old_content_sha, diff_file.old_path, project, replaced: true)

View File

@ -3,7 +3,7 @@
- plain = local_assigns.fetch(:plain, false)
- discussions = local_assigns.fetch(:discussions, nil)
- line_code = diff_file.line_code(line)
- if discussions && line.discussable?
- if discussions
- line_discussions = discussions[line_code]
%tr.line_holder{ class: line.type, id: (line_code unless plain) }
@ -15,11 +15,12 @@
%td.new_line.diff-line-num
%td.line_content.match= line.text
- else
%td.old_line.diff-line-num{ class: [line.type, ("js-avatar-container" if !plain)], data: { linenumber: line.old_pos } }
%td.old_line.diff-line-num{ class: [line.type, ("js-avatar-container" unless plain)], data: { linenumber: line.old_pos } }
- if plain
= diff_link_number(line.type, "new", line.old_pos)
- else
= add_diff_note_button(line_code, diff_file.position(line), line.type)
- if line.discussable?
= add_diff_note_button(line_code, diff_file.position(line), line.type)
%a{ href: "##{line_code}", data: { linenumber: diff_link_number(line.type, "new", line.old_pos) } }
%td.new_line.diff-line-num{ class: line.type, data: { linenumber: line.new_pos } }

View File

@ -1,8 +1,8 @@
---
name: jupyter_clean_diffs
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71477
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343433
milestone: '14.5'
name: rendered_diffs_viewer
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75500
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352831
milestone: '14.9'
type: development
group: group::incubation
default_enabled: true
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: security_report_ingestion_framework
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66735
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343332
milestone: '14.4'
type: development
group: group::threat insights
default_enabled: true

View File

@ -1524,6 +1524,11 @@ On each node:
# Gitaly Auth Token
# Should be the same as praefect_internal_token
gitaly['auth_token'] = '<praefect_internal_token>'
# Gitaly Pack-objects cache
# Recommended to be enabled for improved performance but can notably increase disk I/O
# Refer to https://docs.gitlab.com/ee/administration/gitaly/configure_gitaly.html#pack-objects-cache for more info
gitaly['pack_objects_cache_enabled'] = true
```
1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:

View File

@ -1528,6 +1528,11 @@ On each node:
# Gitaly Auth Token
# Should be the same as praefect_internal_token
gitaly['auth_token'] = '<praefect_internal_token>'
# Gitaly Pack-objects cache
# Recommended to be enabled for improved performance but can notably increase disk I/O
# Refer to https://docs.gitlab.com/ee/administration/gitaly/configure_gitaly.html#pack-objects-cache for more info
gitaly['pack_objects_cache_enabled'] = true
```
1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:

View File

@ -1468,6 +1468,11 @@ On each node:
# Gitaly Auth Token
# Should be the same as praefect_internal_token
gitaly['auth_token'] = '<praefect_internal_token>'
# Gitaly Pack-objects cache
# Recommended to be enabled for improved performance but can notably increase disk I/O
# Refer to https://docs.gitlab.com/ee/administration/gitaly/configure_gitaly.html#pack-objects-cache for more info
gitaly['pack_objects_cache_enabled'] = true
```
1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:

View File

@ -1537,6 +1537,11 @@ On each node:
# Gitaly Auth Token
# Should be the same as praefect_internal_token
gitaly['auth_token'] = '<praefect_internal_token>'
# Gitaly Pack-objects cache
# Recommended to be enabled for improved performance but can notably increase disk I/O
# Refer to https://docs.gitlab.com/ee/administration/gitaly/configure_gitaly.html#pack-objects-cache for more info
gitaly['pack_objects_cache_enabled'] = true
```
1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:

View File

@ -1466,6 +1466,11 @@ On each node:
# Gitaly Auth Token
# Should be the same as praefect_internal_token
gitaly['auth_token'] = '<praefect_internal_token>'
# Gitaly Pack-objects cache
# Recommended to be enabled for improved performance but can notably increase disk I/O
# Refer to https://docs.gitlab.com/ee/administration/gitaly/configure_gitaly.html#pack-objects-cache for more info
gitaly['pack_objects_cache_enabled'] = true
```
1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:

View File

@ -385,6 +385,12 @@ You can group the jobs by:
[Multi-project pipeline graphs](multi_project_pipelines.md#multi-project-pipeline-visualization) help
you visualize the entire pipeline, including all cross-project inter-dependencies. **(PREMIUM)**
If a stage contains more than 100 jobs, only the first 100 jobs are listed in the
pipeline graph. The remaining jobs still run as normal. To see the jobs:
- Select the pipeline, and the jobs are listed on the right side of the pipeline details page.
- On the left sidebar, select **CI/CD > Jobs**.
### View job dependencies in the pipeline graph
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/298973) in GitLab 13.12.

View File

@ -8,11 +8,17 @@ module Banzai
# Find every image that isn't already wrapped in an `a` tag, create
# a new node (a link to the image source), copy the image as a child
# of the anchor, and then replace the img with the link-wrapped version.
#
# If `link_replaces_image` context parameter is provided, the image is going
# to be replaced with a link to an image.
def call
doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |img|
link_replaces_image = !!context[:link_replaces_image]
html_class = link_replaces_image ? 'with-attachment-icon' : 'no-attachment-icon'
link = doc.document.create_element(
'a',
class: 'no-attachment-icon',
class: html_class,
href: img['data-src'] || img['src'],
target: '_blank',
rel: 'noopener noreferrer'
@ -21,7 +27,11 @@ module Banzai
# make sure the original non-proxied src carries over to the link
link['data-canonical-src'] = img['data-canonical-src'] if img['data-canonical-src']
link.children = img.clone
link.children = if link_replaces_image
img['alt'] || img['data-src'] || img['src']
else
img.clone
end
img.replace(link)
end

View File

@ -16,10 +16,12 @@ module Gitlab
end
def transformed_diff(before, after)
Gitlab::AppLogger.info({ message: 'IPYNB_DIFF_GENERATED' })
transformed_diff = IpynbDiff.diff(before, after,
diff_opts: { context: 5, include_diff_info: true },
transform_options: { cell_decorator: :percent },
raise_if_invalid_notebook: true)
raise_if_invalid_nb: true,
diffy_opts: { include_diff_info: true }).to_s(:text)
strip_diff_frontmatter(transformed_diff)
end
@ -29,9 +31,7 @@ module Gitlab
def transformed_blob_data(blob)
if transformed_for_diff?(blob)
IpynbDiff.transform(blob.data,
raise_errors: true,
options: { include_metadata: false, cell_decorator: :percent })
IpynbDiff.transform(blob.data, raise_errors: true, include_frontmatter: false)
end
end

View File

@ -44,11 +44,15 @@ module Gitlab
new_blob_lazy
old_blob_lazy
diff.diff = Gitlab::Diff::CustomDiff.preprocess_before_diff(diff.new_path, old_blob_lazy, new_blob_lazy) || diff.diff if use_custom_diff?
diff.diff = Gitlab::Diff::CustomDiff.preprocess_before_diff(diff.new_path, old_blob_lazy, new_blob_lazy) || diff.diff unless use_renderable_diff?
end
def use_custom_diff?
strong_memoize(:_custom_diff_enabled) { Feature.enabled?(:jupyter_clean_diffs, repository.project, default_enabled: true) }
def use_renderable_diff?
strong_memoize(:_renderable_diff_enabled) { Feature.enabled?(:rendered_diffs_viewer, repository.project, default_enabled: :yaml) }
end
def has_renderable?
rendered&.has_renderable?
end
def position(position_marker, position_type: :text)
@ -370,6 +374,12 @@ module Gitlab
lines.none? { |line| line.type.to_s == 'match' }
end
def rendered
return unless use_renderable_diff? && ipynb?
strong_memoize(:rendered) { Rendered::Notebook::DiffFile.new(self) }
end
private
def diffable_by_attribute?
@ -399,6 +409,10 @@ module Gitlab
new_file? || deleted_file? || content_changed?
end
def ipynb?
modified_file? && file_path.ends_with?('.ipynb')
end
# We can't use Object#try because Blob doesn't inherit from Object, but
# from BasicObject (via SimpleDelegator).
def try_blobs(meth)

View File

@ -11,7 +11,7 @@ module Gitlab
delegate :count, :size, :real_size, to: :raw_diff_files
def self.default_options
::Commit.max_diff_options.merge(ignore_whitespace_change: false, expanded: false, include_stats: true)
::Commit.max_diff_options.merge(ignore_whitespace_change: false, expanded: false, include_stats: true, use_extra_viewer_as_main: false)
end
def initialize(diffable, project:, diff_options: nil, diff_refs: nil, fallback_diff_refs: nil)
@ -25,6 +25,7 @@ module Gitlab
@diff_refs = diff_refs
@fallback_diff_refs = fallback_diff_refs
@repository = project.repository
@use_extra_viewer_as_main = diff_options.delete(:use_extra_viewer_as_main)
end
def diffs
@ -120,11 +121,17 @@ module Gitlab
stats = diff_stats_collection&.find_by_path(diff.new_path)
Gitlab::Diff::File.new(diff,
diff_file = Gitlab::Diff::File.new(diff,
repository: project.repository,
diff_refs: diff_refs,
fallback_diff_refs: fallback_diff_refs,
stats: stats)
if @use_extra_viewer_as_main && diff_file.has_renderable?
diff_file.rendered
else
diff_file
end
end
def sort_diffs(diffs)

View File

@ -12,10 +12,11 @@ module Gitlab
@merge_request_diff = merge_request_diff
super(merge_request_diff,
project: merge_request_diff.project,
diff_options: diff_options,
diff_refs: merge_request_diff.diff_refs,
fallback_diff_refs: merge_request_diff.fallback_diff_refs)
project: merge_request_diff.project,
diff_options: diff_options,
diff_refs: merge_request_diff.diff_refs,
fallback_diff_refs: merge_request_diff.fallback_diff_refs
)
end
def diff_files(sorted: false)

View File

@ -8,9 +8,9 @@ module Gitlab
#
SERIALIZE_KEYS = %i(line_code rich_text text type index old_pos new_pos).freeze
attr_reader :line_code, :marker_ranges
attr_writer :text, :rich_text
attr_accessor :index, :type, :old_pos, :new_pos
attr_reader :marker_ranges
attr_writer :text, :rich_text, :discussable
attr_accessor :index, :type, :old_pos, :new_pos, :line_code
def initialize(text, type, index, old_pos, new_pos, parent_file: nil, line_code: nil, rich_text: nil)
@text = text
@ -26,6 +26,7 @@ module Gitlab
@line_code = line_code || calculate_line_code
@marker_ranges = []
@discussable = true
end
def self.init_from_hash(hash)
@ -96,7 +97,7 @@ module Gitlab
end
def discussable?
!meta?
@discussable && !meta?
end
def suggestible?

View File

@ -0,0 +1,126 @@
# frozen_string_literal: true
module Gitlab
module Diff
module Rendered
module Notebook
include Gitlab::Utils::StrongMemoize
class DiffFile < Gitlab::Diff::File
attr_reader :source_diff
delegate :repository, :diff_refs, :fallback_diff_refs, :unfolded, :unique_identifier,
to: :source_diff
def initialize(diff_file)
@source_diff = diff_file
end
def old_blob
return unless notebook_diff
strong_memoize(:old_blob) { ::Blobs::Notebook.decorate(source_diff.old_blob, notebook_diff.from.as_text) }
end
def new_blob
return unless notebook_diff
strong_memoize(:new_blob) { ::Blobs::Notebook.decorate(source_diff.new_blob, notebook_diff.to.as_text) }
end
def diff
strong_memoize(:diff) { transformed_diff }
end
def has_renderable?
!notebook_diff.nil? && diff.diff.present?
end
def rendered
self
end
def highlighted_diff_lines
@highlighted_diff_lines ||= begin
removal_line_maps, addition_line_maps = compute_end_start_map
Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight.map do |line|
mutate_line(line, addition_line_maps, removal_line_maps)
end
end
end
private
def notebook_diff
strong_memoize(:notebook_diff) do
Gitlab::AppLogger.info({ message: 'IPYNB_DIFF_GENERATED' })
IpynbDiff.diff(source_diff.old_blob&.data, source_diff.new_blob&.data,
raise_if_invalid_nb: true,
diffy_opts: { include_diff_info: true })
rescue IpynbDiff::InvalidNotebookError => e
Gitlab::ErrorTracking.log_exception(e)
nil
end
end
def transformed_diff
return unless notebook_diff
diff = source_diff.diff.clone
diff.diff = strip_diff_frontmatter(notebook_diff.to_s(:text))
diff
end
def strip_diff_frontmatter(diff_content)
diff_content.scan(/.*\n/)[2..]&.join('') if diff_content.present?
end
def transformed_line_to_source(transformed_line, transformed_blocks)
transformed_blocks.empty? ? 0 : ( transformed_blocks[transformed_line - 1][:source_line] || -1 ) + 1
end
def mutate_line(line, addition_line_maps, removal_line_maps)
line.new_pos = transformed_line_to_source(line.new_pos, notebook_diff.to.blocks)
line.old_pos = transformed_line_to_source(line.old_pos, notebook_diff.from.blocks)
line.old_pos = addition_line_maps[line.new_pos] if line.old_pos == 0 && line.new_pos != 0
line.new_pos = removal_line_maps[line.old_pos] if line.new_pos == 0 && line.old_pos != 0
# Lines that do not appear on the original diff should not be commentable
unless addition_line_maps[line.new_pos] || removal_line_maps[line.old_pos]
line.discussable = false
end
line.line_code = line_code(line)
line
end
def compute_end_start_map
# line_codes are used for assigning notes to diffs, and these depend on the line on the new version and the
# line that would have been that one in the previous version. However, since we do a transformation on the
# file, that map gets lost. To overcome this, we look at the original source lines and build two maps:
# - For additions, we look at the latest line change for that line and pick the old line for that id
# - For removals, we look at the first line in the old version, and pick the first line on the new version
#
#
# The caveat here is that we can't have notes on lines that are not a translation of a line in the source
# diff
#
# (gitlab/diff/file.rb:75)
removals = {}
additions = {}
source_diff.highlighted_diff_lines.each do |line|
removals[line.old_pos] = line.new_pos
additions[line.new_pos] = line.old_pos
end
[removals, additions]
end
end
end
end
end
end

View File

@ -56,8 +56,8 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "2.5.0",
"@gitlab/ui": "36.7.0",
"@gitlab/svgs": "2.6.0",
"@gitlab/ui": "36.7.1",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "6.1.4-6",
"@rails/ujs": "6.1.4-6",

View File

@ -0,0 +1,85 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.shared_examples "no multiple viewers" do |commit_ref|
let(:ref) { commit_ref }
it "does not display multiple diff viewers" do
expect(page).not_to have_selector '[data-diff-toggle-entity]'
end
end
RSpec.describe 'Multiple view Diffs', :js do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
let(:ref) { '5d6ed1503801ca9dc28e95eeb85a7cf863527aee' }
let(:path) { project_commit_path(project, ref) }
let(:feature_flag_on) { false }
before do
stub_feature_flags(rendered_diffs_viewer: feature_flag_on ? project : false)
visit path
wait_for_all_requests
end
context 'when :rendered_diffs_viewer is off' do
context 'and diff does not have ipynb' do
include_examples "no multiple viewers", 'ddd0f15ae83993f5cb66a927a28673882e99100b'
end
context 'and diff has ipynb' do
include_examples "no multiple viewers", '5d6ed1503801ca9dc28e95eeb85a7cf863527aee'
it 'shows the transformed diff' do
diff = page.find('.diff-file, .file-holder', match: :first)
expect(diff['innerHTML']).to include('%% Cell type:markdown id:0aac5da7-745c-4eda-847a-3d0d07a1bb9b tags:')
end
end
end
context 'when :rendered_diffs_viewer is on' do
let(:feature_flag_on) { true }
context 'and diff does not include ipynb' do
include_examples "no multiple viewers", 'ddd0f15ae83993f5cb66a927a28673882e99100b'
end
context 'and opening a diff with ipynb' do
context 'but the changes are not renderable' do
include_examples "no multiple viewers", 'a867a602d2220e5891b310c07d174fbe12122830'
end
it 'loads the rendered diff as hidden' do
diff = page.find('.diff-file, .file-holder', match: :first)
expect(diff).to have_selector '[data-diff-toggle-entity="toHide"]'
expect(diff).not_to have_selector '[data-diff-toggle-entity="toShow"]'
expect(classes_for_element(diff, 'toShow', visible: false)).to include('hidden')
expect(classes_for_element(diff, 'toHide')).not_to include('hidden')
expect(classes_for_element(diff, 'toHideBtn')).to include('selected')
expect(classes_for_element(diff, 'toShowBtn')).not_to include('selected')
end
it 'displays the rendered diff and hides after selection changes' do
diff = page.find('.diff-file, .file-holder', match: :first)
diff.find('[data-diff-toggle-entity="toShowBtn"]').click
expect(diff).to have_selector '[data-diff-toggle-entity="toShow"]'
expect(diff).not_to have_selector '[data-diff-toggle-entity="toHide"]'
expect(classes_for_element(diff, 'toHideBtn')).not_to include('selected')
expect(classes_for_element(diff, 'toShowBtn')).to include('selected')
end
end
end
def classes_for_element(node, data_diff_entity, visible: true)
node.find("[data-diff-toggle-entity=\"#{data_diff_entity}\"]", visible: visible)[:class]
end
end

View File

@ -1,3 +1,4 @@
export default function flushPromises() {
// eslint-disable-next-line no-restricted-syntax
return new Promise(setImmediate);
}

View File

@ -25,6 +25,7 @@ const onRequest = () => {
// Use setImmediate to alloow the response interceptor to finish
const onResponse = (config) => {
activeRequests -= 1;
// eslint-disable-next-line no-restricted-syntax
setImmediate(() => {
events.emit('response', config);
});
@ -43,6 +44,7 @@ const subscribeToResponse = (predicate = () => true) =>
// If a request has been made synchronously, setImmediate waits for it to be
// processed and the counter incremented.
// eslint-disable-next-line no-restricted-syntax
setImmediate(listener);
});

View File

@ -116,6 +116,7 @@ export default (
payload,
);
// eslint-disable-next-line no-restricted-syntax
return (result || new Promise((resolve) => setImmediate(resolve)))
.catch((error) => {
validateResults();

View File

@ -123,6 +123,7 @@ class CustomEnvironment extends JSDOMEnvironment {
// Reset `Date` so that Jest can report timing accurately *roll eyes*...
setGlobalDateToRealDate();
// eslint-disable-next-line no-restricted-syntax
await new Promise(setImmediate);
if (this.rejectedPromises.length > 0) {

View File

@ -8,6 +8,7 @@ initializeTestTimeout(process.env.CI ? 6000 : 500);
afterEach(() =>
// give Promises a bit more time so they fail the right test
// eslint-disable-next-line no-restricted-syntax
new Promise(setImmediate).then(() => {
// wait for pending setTimeout()s
jest.runOnlyPendingTimers();

View File

@ -6,17 +6,25 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
include GraphqlHelpers
describe '#resolve' do
before do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_return(fake_result)
allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint_double)
end
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
let_it_be(:sha) { nil }
let_it_be(:content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
end
let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_return(fake_result)
ci_lint_double
end
before do
allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
end
subject(:response) do
resolve(described_class,
args: { project_path: project.full_path, content: content, sha: sha },
@ -33,10 +41,6 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
)
end
let_it_be(:content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
end
it 'lints the ci config file and returns the merged yaml file' do
expect(response[:status]).to eq(:valid)
expect(response[:merged_yaml]).to eq(content)
@ -74,5 +78,23 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
expect(response[:errors]).to eq(['Invalid configuration format'])
end
end
context 'with an invalid SHA' do
let_it_be(:sha) { ':' }
let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument)
ci_lint_double
end
it 'logs the invalid SHA to Sentry' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
.with(GRPC::InvalidArgument, sha: ':')
response
end
end
end
end

View File

@ -86,6 +86,31 @@ RSpec.describe CommitsHelper do
end
end
describe '#diff_mode_swap_button' do
let(:keyword) { 'rendered' }
let(:node) { Nokogiri::HTML.parse(helper.diff_mode_swap_button(keyword, 'abc')).at_css('a') }
context 'for rendered' do
it 'renders the correct select-rendered button' do
expect(node[:title]).to eq('Display rendered diff')
expect(node['data-file-hash']).to eq('abc')
expect(node['data-diff-toggle-entity']).to eq('toShowBtn')
expect(node.xpath("//a/svg")[0]["data-testid"]).to eq('doc-text-icon')
end
end
context 'for raw' do
let(:keyword) { 'raw' }
it 'renders the correct select-raw button' do
expect(node[:title]).to eq('Display raw diff')
expect(node['data-file-hash']).to eq('abc')
expect(node['data-diff-toggle-entity']).to eq('toHideBtn')
expect(node.xpath("//a/svg")[0]["data-testid"]).to eq('doc-code-icon')
end
end
end
describe '#commit_to_html' do
let(:project) { create(:project, :repository) }
let(:ref) { 'master' }

View File

@ -5,34 +5,82 @@ require 'spec_helper'
RSpec.describe Banzai::Filter::ImageLinkFilter do
include FilterSpecHelper
def image(path)
%(<img src="#{path}" />)
let(:path) { '/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg' }
let(:context) { {} }
def image(path, alt: nil, data_src: nil)
alt_tag = alt ? %Q{alt="#{alt}"} : ""
data_src_tag = data_src ? %Q{data-src="#{data_src}"} : ""
%(<img src="#{path}" #{alt_tag} #{data_src_tag} />)
end
it 'wraps the image with a link to the image src' do
doc = filter(image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg'))
doc = filter(image(path), context)
expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
end
it 'does not wrap a duplicate link' do
doc = filter(%Q(<a href="/whatever">#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')}</a>))
doc = filter(%Q(<a href="/whatever">#{image(path)}</a>), context)
expect(doc.to_html).to match %r{^<a href="/whatever"><img[^>]*></a>$}
end
it 'works with external images' do
doc = filter(image('https://i.imgur.com/DfssX9C.jpg'))
doc = filter(image('https://i.imgur.com/DfssX9C.jpg'), context)
expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
end
it 'works with inline images' do
doc = filter(%Q(<p>test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline</p>))
doc = filter(%Q(<p>test #{image(path)} inline</p>), context)
expect(doc.to_html).to match %r{^<p>test <a[^>]*><img[^>]*></a> inline</p>$}
end
it 'keep the data-canonical-src' do
doc = filter(%q(<img src="http://assets.example.com/6cd/4d7" data-canonical-src="http://example.com/test.png" />))
doc = filter(%q(<img src="http://assets.example.com/6cd/4d7" data-canonical-src="http://example.com/test.png" />), context)
expect(doc.at_css('img')['src']).to eq doc.at_css('a')['href']
expect(doc.at_css('img')['data-canonical-src']).to eq doc.at_css('a')['data-canonical-src']
end
it 'adds no-attachment icon class to the link' do
doc = filter(image(path), context)
expect(doc.at_css('a')['class']).to match(%r{no-attachment-icon})
end
context 'when :link_replaces_image is true' do
let(:context) { { link_replaces_image: true } }
it 'replaces the image with link to image src', :aggregate_failures do
doc = filter(image(path), context)
expect(doc.to_html).to match(%r{^<a[^>]*>#{path}</a>$})
expect(doc.at_css('a')['href']).to eq(path)
end
it 'uses image alt as a link text', :aggregate_failures do
doc = filter(image(path, alt: 'My image'), context)
expect(doc.to_html).to match(%r{^<a[^>]*>My image</a>$})
expect(doc.at_css('a')['href']).to eq(path)
end
it 'uses image data-src as a link text', :aggregate_failures do
data_src = '/uploads/data-src.png'
doc = filter(image(path, data_src: data_src), context)
expect(doc.to_html).to match(%r{^<a[^>]*>#{data_src}</a>$})
expect(doc.at_css('a')['href']).to eq(data_src)
end
it 'adds attachment icon class to the link' do
doc = filter(image(path), context)
expect(doc.at_css('a')['class']).to match(%r{with-attachment-icon})
end
end
end

View File

@ -51,45 +51,29 @@ RSpec.describe Gitlab::Diff::File do
project.commit(branch_name).diffs.diff_files.first
end
describe 'initialize' do
context 'when file is ipynb with a change after transformation' do
describe '#has_renderable?' do
context 'file is ipynb' do
let(:commit) { project.commit("532c837") }
let(:diff) { commit.raw_diffs.first }
let(:diff_file) { described_class.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
context 'and :jupyter_clean_diffs is enabled' do
before do
stub_feature_flags(jupyter_clean_diffs: true)
end
it 'recreates the diff by transforming the files' do
expect(diff_file.diff.diff).not_to include('cell_type')
end
end
context 'but :jupyter_clean_diffs is disabled' do
before do
stub_feature_flags(jupyter_clean_diffs: false)
end
it 'does not recreate the diff' do
expect(diff_file.diff.diff).to include('cell_type')
end
it 'has renderable viewer' do
expect(diff_file.has_renderable?).to be_truthy
end
end
context 'when file is ipynb, but there only changes that are removed' do
let(:commit) { project.commit("2b5ef814") }
let(:diff) { commit.raw_diffs.first }
let(:diff_file) { described_class.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
context 'file is not ipynb' do
let(:commit) { project.commit("d59c60028b053793cecfb4022de34602e1a9218e") }
before do
stub_feature_flags(jupyter_clean_diffs: true)
it 'does not have renderable viewer' do
expect(diff_file.has_renderable?).to be_falsey
end
end
end
it 'does not recreate the diff' do
expect(diff_file.diff.diff).to include('execution_count')
end
describe '#rendered' do
let(:commit) { project.commit("532c837") }
it 'creates a NotebookDiffFile for rendering' do
expect(diff_file.rendered).to be_kind_of(Gitlab::Diff::Rendered::Notebook::DiffFile)
end
end

View File

@ -0,0 +1,107 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFile do
include RepoHelpers
let(:project) { create(:project, :repository) }
let(:commit) { project.commit("5d6ed1503801ca9dc28e95eeb85a7cf863527aee") }
let(:diffs) { commit.raw_diffs.to_a }
let(:diff) { diffs.first }
let(:source) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
let(:nb_file) { described_class.new(source) }
describe '#old_blob and #new_blob' do
context 'when file is changed' do
it 'transforms the old blob' do
expect(nb_file.old_blob.data).to include('%%')
end
it 'transforms the new blob' do
expect(nb_file.new_blob.data).to include('%%')
end
end
context 'when file is added' do
let(:diff) { diffs[1] }
it 'old_blob is empty' do
expect(nb_file.old_blob).to be_nil
end
it 'new_blob is transformed' do
expect(nb_file.new_blob.data).to include('%%')
end
end
context 'when file is removed' do
let(:diff) { diffs[2] }
it 'old_blob is transformed' do
expect(nb_file.old_blob.data).to include('%%')
end
it 'new_blob is empty' do
expect(nb_file.new_blob).to be_nil
end
end
end
describe '#diff' do
context 'for valid notebooks' do
it 'returns the transformed diff' do
expect(nb_file.diff.diff).to include('%%')
end
end
context 'for invalid notebooks' do
let(:commit) { project.commit("6d85bb693dddaee631ec0c2f697c52c62b93f6d3") }
let(:diff) { diffs[1] }
it 'returns nil' do
expect(nb_file.diff).to be_nil
end
end
end
describe '#has_renderable?' do
context 'notebook diff is empty' do
let(:commit) { project.commit("a867a602d2220e5891b310c07d174fbe12122830") }
it 'is false' do
expect(nb_file.has_renderable?).to be_falsey
end
end
context 'notebook is valid' do
it 'is true' do
expect(nb_file.has_renderable?).to be_truthy
end
end
end
describe '#highlighted_diff_lines?' do
context 'when line transformed line is not part of the diff' do
it 'line is not discussable' do
expect(nb_file.highlighted_diff_lines[0].discussable?).to be_falsey
end
end
context 'when line transformed line part of the diff' do
it 'line is not discussable' do
expect(nb_file.highlighted_diff_lines[12].discussable?).to be_truthy
end
end
context 'assigns the correct position' do
it 'computes de first line where the remove would appear' do
expect(nb_file.highlighted_diff_lines[0].old_pos).to eq(3)
expect(nb_file.highlighted_diff_lines[0].new_pos).to eq(3)
expect(nb_file.highlighted_diff_lines[12].new_pos).to eq(15)
expect(nb_file.highlighted_diff_lines[12].old_pos).to eq(18)
end
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Blobs::NotebookPresenter do
include RepoHelpers
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:blob) { repository.blob_at('HEAD', 'files/ruby/regex.rb') }
let(:user) { project.first_owner }
let(:git_blob) { blob.__getobj__ }
subject(:presenter) { described_class.new(blob, current_user: user) }
it 'highlight receives markdown' do
expect(Gitlab::Highlight).to receive(:highlight).with('files/ruby/regex.rb', git_blob.data, plain: nil, language: 'md')
presenter.highlight
end
end

View File

@ -54,7 +54,7 @@ module TestEnv
'wip' => 'b9238ee',
'csv' => '3dd0896',
'v1.1.0' => 'b83d6e3',
'add-ipython-files' => '532c837',
'add-ipython-files' => 'a867a602',
'add-pdf-file' => 'e774ebd',
'squash-large-files' => '54cec52',
'add-pdf-text-binary' => '79faa7b',

View File

@ -981,20 +981,20 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.1.0"
"@gitlab/svgs@2.5.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.5.0.tgz#e0569916fa858462b1801cc90ef8dd9706a12e96"
integrity sha512-cH/EBs//wdkH6kG+kDpvRCIl63/A8JgjAhBJ+ZWucPgtNCDD6x6RDMGdQrxSqhYwcCKDoLStfcxmblBkuiSRXQ==
"@gitlab/svgs@2.6.0":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.6.0.tgz#49f020b0a732f5df01138bd21b610a0a940badd6"
integrity sha512-jI8CHlrriePtUsognRkpZQWVsZe7ZjytmKakeYyU1aKvsnJ4fAeySlVkCAiqKbbZm3T/eeH/6b3jxHn24U0k0Q==
"@gitlab/ui@36.7.0":
version "36.7.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-36.7.0.tgz#0b4c5709bf0a9be9a24516a32181526b8367777d"
integrity sha512-IVY6uz5ICozLVJS7t0Pd70qyzMzNNiEULlKVcMoYEEkQQ0Iu/qKj016e5Xm8i9wheSqG2l/fMzUxuNbtftGu2w==
"@gitlab/ui@36.7.1":
version "36.7.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-36.7.1.tgz#49005f55e3fedc3c3ca054355d29c7ac5ade94b5"
integrity sha512-MIohLWzZtKxUVwxB26em/R7JB8RB8a/BbTDZR/06xp8yMa4wpNtGTuTGajBKBnbltdKE1h1jVIOLF9Kkb3Njow==
dependencies:
"@babel/standalone" "^7.0.0"
bootstrap-vue "2.20.1"
copy-to-clipboard "^3.0.8"
dompurify "^2.3.5"
dompurify "^2.3.6"
echarts "^5.2.1"
highlight.js "^10.6.0"
iframe-resizer "^4.3.2"
@ -4953,7 +4953,7 @@ dompurify@2.3.4:
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.4.tgz#1cf5cf0105ccb4debdf6db162525bd41e6ddacc6"
integrity sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ==
dompurify@^2.3.5, dompurify@^2.3.6:
dompurify@^2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.6.tgz#2e019d7d7617aacac07cbbe3d88ae3ad354cf875"
integrity sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==