Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
59f160b0cf
commit
95b9a603c3
|
@ -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/**/*'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
c1cabced28d02d667a37122860adde10068d28e2
|
||||
51cf676fc530f242eb893c7b630ef309de116e35
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -534,4 +534,4 @@ gem 'ipaddress', '~> 0.8.3'
|
|||
|
||||
gem 'parslet', '~> 1.8'
|
||||
|
||||
gem 'ipynbdiff', '0.3.8'
|
||||
gem 'ipynbdiff', '0.4.2'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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');
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# frozen_string_literal: true
|
||||
require 'ipynbdiff'
|
||||
|
||||
class BlobPresenter < Gitlab::View::Presenter::Delegated
|
||||
include ApplicationHelper
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Blobs
|
||||
class NotebookPresenter < ::BlobPresenter
|
||||
def gitattr_language
|
||||
'md'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 } }
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
@ -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
|
|
@ -1,3 +1,4 @@
|
|||
export default function flushPromises() {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return new Promise(setImmediate);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -116,6 +116,7 @@ export default (
|
|||
payload,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return (result || new Promise((resolve) => setImmediate(resolve)))
|
||||
.catch((error) => {
|
||||
validateResults();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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',
|
||||
|
|
20
yarn.lock
20
yarn.lock
|
@ -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==
|
||||
|
|
Loading…
Reference in New Issue