Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml6
-rw-r--r--.gitlab/ci/vendored-gems.gitlab-ci.yml7
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock12
-rw-r--r--app/finders/clusters/agents_finder.rb11
-rw-r--r--app/graphql/resolvers/clusters/agents_resolver.rb2
-rw-r--r--app/models/ci/runner.rb14
-rw-r--r--app/serializers/ci/job_entity.rb1
-rw-r--r--app/workers/namespaces/process_sync_events_worker.rb2
-rw-r--r--app/workers/projects/process_sync_events_worker.rb2
-rw-r--r--db/post_migrate/20220601151900_schedule_backfill_ci_runner_semver.rb20
-rw-r--r--db/post_migrate/20220624062300_delete_backfill_ci_runner_semver_migration.rb19
-rw-r--r--db/schema_migrations/202206240623001
-rw-r--r--doc/api/graphql/reference/index.md52
-rw-r--r--package.json2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb2
-rw-r--r--spec/models/ci/runner_spec.rb53
-rw-r--r--spec/models/repository_spec.rb11
-rw-r--r--spec/serializers/ci/job_entity_spec.rb4
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb2
-rw-r--r--vendor/gems/ipynbdiff/.gitignore2
-rw-r--r--vendor/gems/ipynbdiff/.gitlab-ci.yml32
-rw-r--r--vendor/gems/ipynbdiff/Gemfile5
-rw-r--r--vendor/gems/ipynbdiff/Gemfile.lock64
-rw-r--r--vendor/gems/ipynbdiff/LICENSE21
-rw-r--r--vendor/gems/ipynbdiff/README.md56
-rw-r--r--vendor/gems/ipynbdiff/ipynbdiff.gemspec33
-rw-r--r--vendor/gems/ipynbdiff/lib/diff.rb20
-rw-r--r--vendor/gems/ipynbdiff/lib/ipynb_symbol_map.rb218
-rw-r--r--vendor/gems/ipynbdiff/lib/ipynbdiff.rb23
-rw-r--r--vendor/gems/ipynbdiff/lib/output_transformer.rb83
-rw-r--r--vendor/gems/ipynbdiff/lib/symbolized_markdown_helper.rb26
-rw-r--r--vendor/gems/ipynbdiff/lib/transformed_notebook.rb20
-rw-r--r--vendor/gems/ipynbdiff/lib/transformer.rb101
-rw-r--r--vendor/gems/ipynbdiff/lib/version.rb5
-rw-r--r--vendor/gems/ipynbdiff/spec/ipynb_symbol_map_spec.rb165
-rw-r--r--vendor/gems/ipynbdiff/spec/ipynbdiff_spec.rb126
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected.md7
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected_symbols.txt7
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/input.ipynb16
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md16
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt16
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/error_output/input.ipynb32
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/from.ipynb198
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md12
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt12
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/hide_images/input.ipynb45
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md11
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt11
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/input.ipynb74
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md10
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt10
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/latex_output/input.ipynb34
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md9
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt9
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/input.ipynb25
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/no_cells/expected.md19
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/no_cells/expected_symbols.txt19
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/no_cells/input.ipynb25
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected.md0
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected_symbols.txt0
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/input.ipynb25
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected.md13
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected_symbols.txt13
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/input.ipynb29
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code/expected.md7
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code/expected_symbols.txt7
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code/input.ipynb21
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected.md5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected_symbols.txt5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/input.ipynb12
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected.md5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected_symbols.txt5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/input.ipynb14
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected.md5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected_symbols.txt5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/input.ipynb11
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_md/expected.md5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_md/expected_symbols.txt5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_md/input.ipynb21
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_raw/expected.md4
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_raw/expected_symbols.txt4
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/only_raw/input.ipynb15
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md70
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt70
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected.md3
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected_symbols.txt3
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/single_line_md/input.ipynb17
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md9
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt9
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/stream_text/input.ipynb27
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/svg/expected.md19
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt19
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/svg/input.ipynb66
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md9
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt9
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/text_output/input.ipynb31
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md16
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt16
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/text_png_output/input.ipynb49
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/to.ipynb200
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected.md5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected_symbols.txt5
-rw-r--r--vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/input.ipynb27
-rw-r--r--vendor/gems/ipynbdiff/spec/transformer_spec.rb90
-rw-r--r--yarn.lock18
107 files changed, 2762 insertions, 105 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 6b2cc01bfb8..4b7f60c1bac 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -1495,6 +1495,12 @@
changes: ["vendor/gems/mail-smtp_pool/**/*"]
- <<: *if-merge-request-labels-run-all-rspec
+.vendor:rules:ipynbdiff:
+ rules:
+ - <<: *if-merge-request
+ changes: ["vendor/gems/ipynbdiff/**/*"]
+ - <<: *if-merge-request-labels-run-all-rspec
+
##################
# Releases rules #
##################
diff --git a/.gitlab/ci/vendored-gems.gitlab-ci.yml b/.gitlab/ci/vendored-gems.gitlab-ci.yml
index a39c4307c13..ce71820100f 100644
--- a/.gitlab/ci/vendored-gems.gitlab-ci.yml
+++ b/.gitlab/ci/vendored-gems.gitlab-ci.yml
@@ -5,3 +5,10 @@ vendor mail-smtp_pool:
trigger:
include: vendor/gems/mail-smtp_pool/.gitlab-ci.yml
strategy: depend
+vendor ipynbdiff:
+ extends:
+ - .vendor:rules:ipynbdiff
+ needs: []
+ trigger:
+ include: vendor/gems/ipynbdiff/.gitlab-ci.yml
+ strategy: depend
diff --git a/Gemfile b/Gemfile
index d05013b8fc1..e3eba484a15 100644
--- a/Gemfile
+++ b/Gemfile
@@ -546,6 +546,6 @@ gem 'ipaddress', '~> 0.8.3'
gem 'parslet', '~> 1.8'
-gem 'ipynbdiff', '0.4.7'
+gem 'ipynbdiff', path: 'vendor/gems/ipynbdiff'
gem 'ed25519', '~> 1.3.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 1d1e6eb237a..aea6f448587 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,4 +1,11 @@
PATH
+ remote: vendor/gems/ipynbdiff
+ specs:
+ ipynbdiff (0.4.7)
+ diffy (~> 3.3)
+ json (~> 2.5, >= 2.5.1)
+
+PATH
remote: vendor/gems/mail-smtp_pool
specs:
mail-smtp_pool (0.1.0)
@@ -667,9 +674,6 @@ GEM
invisible_captcha (1.1.0)
rails (>= 4.2)
ipaddress (0.8.3)
- ipynbdiff (0.4.7)
- diffy (~> 3.3)
- json (~> 2.5, >= 2.5.1)
jaeger-client (1.1.0)
opentracing (~> 0.3)
thrift
@@ -1575,7 +1579,7 @@ DEPENDENCIES
icalendar
invisible_captcha (~> 1.1.0)
ipaddress (~> 0.8.3)
- ipynbdiff (= 0.4.7)
+ ipynbdiff!
jira-ruby (~> 2.1.4)
js_regex (~> 3.7)
json (~> 2.5.1)
diff --git a/app/finders/clusters/agents_finder.rb b/app/finders/clusters/agents_finder.rb
index 8bc78300a44..14277db3f85 100644
--- a/app/finders/clusters/agents_finder.rb
+++ b/app/finders/clusters/agents_finder.rb
@@ -13,8 +13,7 @@ module Clusters
def execute
return ::Clusters::Agent.none unless can_read_cluster_agents?
- agents = object.cluster_agents
- agents = agents.with_name(params[:name]) if params[:name].present?
+ agents = filter_clusters(object.cluster_agents)
agents.ordered_by_name
end
@@ -23,8 +22,16 @@ module Clusters
attr_reader :object, :current_user, :params
+ def filter_clusters(agents)
+ agents = agents.with_name(params[:name]) if params[:name].present?
+
+ agents
+ end
+
def can_read_cluster_agents?
current_user&.can?(:read_cluster, object)
end
end
end
+
+Clusters::AgentsFinder.prepend_mod_with('Clusters::AgentsFinder')
diff --git a/app/graphql/resolvers/clusters/agents_resolver.rb b/app/graphql/resolvers/clusters/agents_resolver.rb
index 81cea241944..0b9eb361dbd 100644
--- a/app/graphql/resolvers/clusters/agents_resolver.rb
+++ b/app/graphql/resolvers/clusters/agents_resolver.rb
@@ -34,3 +34,5 @@ module Resolvers
end
end
end
+
+Resolvers::Clusters::AgentsResolver.prepend_mod_with('Resolvers::Clusters::AgentsResolver')
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index e0180880760..c3d948ef9fd 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -15,6 +15,8 @@ module Ci
include Presentable
include EachBatch
+ ignore_column :semver, remove_with: '15.3', remove_after: '2022-07-22'
+
add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration, expiration_enforced?: :token_expiration_enforced?
enum access_level: {
@@ -78,7 +80,6 @@ module Ci
has_one :last_build, -> { order('id DESC') }, class_name: 'Ci::Build'
before_save :ensure_token
- before_save :update_semver, if: -> { version_changed? }
scope :active, -> (value = true) { where(active: value) }
scope :paused, -> { active(false) }
@@ -431,7 +432,6 @@ module Ci
values = values&.slice(:version, :revision, :platform, :architecture, :ip_address, :config, :executor) || {}
values[:contacted_at] = Time.current
values[:executor_type] = EXECUTOR_NAME_TO_TYPES.fetch(values.delete(:executor), :unknown)
- values[:semver] = semver_from_version(values[:version])
cache_attributes(values)
@@ -452,16 +452,6 @@ module Ci
read_attribute(:contacted_at)
end
- def semver_from_version(version)
- parsed_runner_version = ::Gitlab::VersionInfo.parse(version)
-
- parsed_runner_version.valid? ? parsed_runner_version.to_s : nil
- end
-
- def update_semver
- self.semver = semver_from_version(self.version)
- end
-
def namespace_ids
strong_memoize(:namespace_ids) do
runner_namespaces.pluck(:namespace_id).compact
diff --git a/app/serializers/ci/job_entity.rb b/app/serializers/ci/job_entity.rb
index a865fc99458..813938c2a18 100644
--- a/app/serializers/ci/job_entity.rb
+++ b/app/serializers/ci/job_entity.rb
@@ -42,6 +42,7 @@ module Ci
expose :scheduled_at, if: -> (*) { scheduled? }
expose :created_at
expose :queued_at
+ expose :queued_duration
expose :updated_at
expose :detailed_status, as: :status, with: DetailedStatusEntity
expose :callout_message, if: -> (*) { failed? && !job.script_failure? }
diff --git a/app/workers/namespaces/process_sync_events_worker.rb b/app/workers/namespaces/process_sync_events_worker.rb
index 36c4ab2058d..2bf2a4a6ef8 100644
--- a/app/workers/namespaces/process_sync_events_worker.rb
+++ b/app/workers/namespaces/process_sync_events_worker.rb
@@ -13,7 +13,7 @@ module Namespaces
urgency :high
idempotent!
- deduplicate :until_executed
+ deduplicate :until_executing
def perform
results = ::Ci::ProcessSyncEventsService.new(
diff --git a/app/workers/projects/process_sync_events_worker.rb b/app/workers/projects/process_sync_events_worker.rb
index 92322a9ea99..57f3e3dee5e 100644
--- a/app/workers/projects/process_sync_events_worker.rb
+++ b/app/workers/projects/process_sync_events_worker.rb
@@ -13,7 +13,7 @@ module Projects
urgency :high
idempotent!
- deduplicate :until_executed
+ deduplicate :until_executing
def perform
results = ::Ci::ProcessSyncEventsService.new(
diff --git a/db/post_migrate/20220601151900_schedule_backfill_ci_runner_semver.rb b/db/post_migrate/20220601151900_schedule_backfill_ci_runner_semver.rb
index 30615d49268..9c62ec1b87b 100644
--- a/db/post_migrate/20220601151900_schedule_backfill_ci_runner_semver.rb
+++ b/db/post_migrate/20220601151900_schedule_backfill_ci_runner_semver.rb
@@ -12,15 +12,17 @@ class ScheduleBackfillCiRunnerSemver < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
- queue_batched_background_migration(
- MIGRATION,
- :ci_runners,
- :id,
- job_interval: INTERVAL,
- batch_size: BATCH_SIZE,
- max_batch_size: MAX_BATCH_SIZE,
- sub_batch_size: SUB_BATCH_SIZE
- )
+ # Disabled background migration introduced in same milestone as it was decided to change approach
+ # and the semver column will no longer be needed
+ # queue_batched_background_migration(
+ # MIGRATION,
+ # :ci_runners,
+ # :id,
+ # job_interval: INTERVAL,
+ # batch_size: BATCH_SIZE,
+ # max_batch_size: MAX_BATCH_SIZE,
+ # sub_batch_size: SUB_BATCH_SIZE
+ # )
end
def down
diff --git a/db/post_migrate/20220624062300_delete_backfill_ci_runner_semver_migration.rb b/db/post_migrate/20220624062300_delete_backfill_ci_runner_semver_migration.rb
new file mode 100644
index 00000000000..4632d9104ea
--- /dev/null
+++ b/db/post_migrate/20220624062300_delete_backfill_ci_runner_semver_migration.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class DeleteBackfillCiRunnerSemverMigration < Gitlab::Database::Migration[2.0]
+ restrict_gitlab_migration gitlab_schema: :gitlab_ci
+
+ MIGRATION = 'BackfillCiRunnerSemver'
+
+ disable_ddl_transaction!
+
+ def up
+ # Disabled background migration introduced in same milestone as it was decided to change approach
+ # and the semver column will no longer be needed
+ delete_batched_background_migration(MIGRATION, :ci_runners, :id, [])
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/schema_migrations/20220624062300 b/db/schema_migrations/20220624062300
new file mode 100644
index 00000000000..a13fda7e34b
--- /dev/null
+++ b/db/schema_migrations/20220624062300
@@ -0,0 +1 @@
+d09b9359b871c96511c255abdc1ff82640420f469a16c5e76461ca47dca58770 \ No newline at end of file
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 2f2eec6399e..b3cdaeea85c 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -11795,7 +11795,6 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupautodevopsenabled"></a>`autoDevopsEnabled` | [`Boolean`](#boolean) | Indicates whether Auto DevOps is enabled for all projects within this group. |
| <a id="groupavatarurl"></a>`avatarUrl` | [`String`](#string) | Avatar URL of the group. |
| <a id="groupcivariables"></a>`ciVariables` | [`CiVariableConnection`](#civariableconnection) | List of the group's CI/CD variables. (see [Connections](#connections)) |
-| <a id="groupclusteragents"></a>`clusterAgents` | [`ClusterAgentConnection`](#clusteragentconnection) | Cluster agents associated with projects in the group and its subgroups. (see [Connections](#connections)) |
| <a id="groupcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the group. |
| <a id="groupcontainslockedprojects"></a>`containsLockedProjects` | [`Boolean!`](#boolean) | Includes at least one project where the repository size exceeds the limit. |
| <a id="groupcrossprojectpipelineavailable"></a>`crossProjectPipelineAvailable` | [`Boolean!`](#boolean) | Indicates if the cross_project_pipeline feature is available for the namespace. |
@@ -11888,6 +11887,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| ---- | ---- | ----------- |
| <a id="groupboardsid"></a>`id` | [`BoardID`](#boardid) | Find a board by its ID. |
+##### `Group.clusterAgents`
+
+Cluster agents associated with projects in the group and its subgroups.
+
+Returns [`ClusterAgentConnection`](#clusteragentconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="groupclusteragentshasvulnerabilities"></a>`hasVulnerabilities` | [`Boolean`](#boolean) | Returns only cluster agents which have vulnerabilities. |
+
##### `Group.codeCoverageActivities`
Represents the code coverage activity for this group.
@@ -12676,12 +12691,27 @@ A block of time for which a participant is on-call.
| Name | Type | Description |
| ---- | ---- | ----------- |
-| <a id="instancesecuritydashboardclusteragents"></a>`clusterAgents` | [`ClusterAgentConnection`](#clusteragentconnection) | Cluster agents associated with projects selected in the Instance Security Dashboard. (see [Connections](#connections)) |
| <a id="instancesecuritydashboardvulnerabilitygrades"></a>`vulnerabilityGrades` | [`[VulnerableProjectsByGrade!]!`](#vulnerableprojectsbygrade) | Represents vulnerable project counts for each grade. |
| <a id="instancesecuritydashboardvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the vulnerabilities from projects selected in Instance Security Dashboard. (see [Connections](#connections)) |
#### Fields with arguments
+##### `InstanceSecurityDashboard.clusterAgents`
+
+Cluster agents associated with projects selected in the Instance Security Dashboard.
+
+Returns [`ClusterAgentConnection`](#clusteragentconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="instancesecuritydashboardclusteragentshasvulnerabilities"></a>`hasVulnerabilities` | [`Boolean`](#boolean) | Returns only cluster agents which have vulnerabilities. |
+
##### `InstanceSecurityDashboard.projects`
Projects selected in Instance Security Dashboard.
@@ -15053,7 +15083,6 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projectciconfigpathordefault"></a>`ciConfigPathOrDefault` | [`String!`](#string) | Path of the CI configuration file. |
| <a id="projectcijobtokenscope"></a>`ciJobTokenScope` | [`CiJobTokenScopeType`](#cijobtokenscopetype) | The CI Job Tokens scope of access. |
| <a id="projectcivariables"></a>`ciVariables` | [`CiVariableConnection`](#civariableconnection) | List of the project's CI/CD variables. (see [Connections](#connections)) |
-| <a id="projectclusteragents"></a>`clusterAgents` | [`ClusterAgentConnection`](#clusteragentconnection) | Cluster agents associated with the project. (see [Connections](#connections)) |
| <a id="projectcodecoveragesummary"></a>`codeCoverageSummary` | [`CodeCoverageSummary`](#codecoveragesummary) | Code coverage summary associated with the project. |
| <a id="projectcomplianceframeworks"></a>`complianceFrameworks` | [`ComplianceFrameworkConnection`](#complianceframeworkconnection) | Compliance frameworks associated with the project. (see [Connections](#connections)) |
| <a id="projectcontainerexpirationpolicy"></a>`containerExpirationPolicy` | [`ContainerExpirationPolicy`](#containerexpirationpolicy) | Container expiration policy of the project. |
@@ -15273,8 +15302,25 @@ Returns [`ClusterAgent`](#clusteragent).
| Name | Type | Description |
| ---- | ---- | ----------- |
+| <a id="projectclusteragenthasvulnerabilities"></a>`hasVulnerabilities` | [`Boolean`](#boolean) | Returns only cluster agents which have vulnerabilities. |
| <a id="projectclusteragentname"></a>`name` | [`String!`](#string) | Name of the cluster agent. |
+##### `Project.clusterAgents`
+
+Cluster agents associated with the project.
+
+Returns [`ClusterAgentConnection`](#clusteragentconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="projectclusteragentshasvulnerabilities"></a>`hasVulnerabilities` | [`Boolean`](#boolean) | Returns only cluster agents which have vulnerabilities. |
+
##### `Project.containerRepositories`
Container repositories of the project.
diff --git a/package.json b/package.json
index 1de50178951..75e58835f6d 100644
--- a/package.json
+++ b/package.json
@@ -251,7 +251,7 @@
"stylelint": "^14.9.1",
"timezone-mock": "^1.0.8",
"vue-jest": "4.0.1",
- "webpack-dev-server": "4.9.2",
+ "webpack-dev-server": "4.9.3",
"xhr-mock": "^2.5.1",
"yarn-check-webpack-plugin": "^1.2.0",
"yarn-deduplicate": "^5.0.0"
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb
index 9c90cf6ae3d..66e2309e173 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Plan', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/293699', type: :bug } do
+ RSpec.describe 'Plan' do
describe 'Assignees' do
let(:user1) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:user2) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) }
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 47688c4b3e6..3081a3960e9 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -525,7 +525,7 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
it 'does not error when dereferenced_target is nil' do
blob_id = repository.blob_at('master', 'README.md').id
- repository_rugged.tags.create("refs/tags/blob-tag", blob_id)
+ repository.add_tag("refs/tags/blob-tag", user: user, target: blob_id)
expect { subject }.not_to raise_error
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index c5cb67929e2..06baad38768 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Ci::Runner do
end
describe 'groups association' do
- # Due to other assoctions such as projects this whole spec is allowed to
+ # Due to other associations such as projects this whole spec is allowed to
# generate cross-database queries. So we have this temporary spec to
# validate that at least groups association does not generate cross-DB
# queries.
@@ -1062,18 +1062,6 @@ RSpec.describe Ci::Runner do
end
end
end
-
- context 'with updated version' do
- before do
- runner.version = '1.2.3'
- end
-
- it 'updates version components with new version' do
- heartbeat
-
- expect(runner.reload.read_attribute(:semver)).to eq '15.0.1'
- end
- end
end
def expect_redis_update
@@ -1088,7 +1076,6 @@ RSpec.describe Ci::Runner do
.and change { runner.reload.read_attribute(:architecture) }
.and change { runner.reload.read_attribute(:config) }
.and change { runner.reload.read_attribute(:executor_type) }
- .and change { runner.reload.read_attribute(:semver) }
end
end
@@ -1733,42 +1720,4 @@ RSpec.describe Ci::Runner do
end
end
end
-
- describe '.save' do
- context 'with initial value' do
- let(:runner) { create(:ci_runner, version: 'v1.2.3') }
-
- it 'updates semver column' do
- expect(runner.semver).to eq '1.2.3'
- end
- end
-
- context 'with no initial version value' do
- let(:runner) { build(:ci_runner) }
-
- context 'with version change' do
- subject(:update_version) { runner.update!(version: new_version) }
-
- context 'to invalid version' do
- let(:new_version) { 'invalid version' }
-
- it 'updates semver column to nil' do
- update_version
-
- expect(runner.reload.semver).to be_nil
- end
- end
-
- context 'to v14.10.1' do
- let(:new_version) { 'v14.10.1' }
-
- it 'updates semver column' do
- update_version
-
- expect(runner.reload.semver).to eq '14.10.1'
- end
- end
- end
- end
- end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index e1d903a40cf..b5db4492ab9 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -125,11 +125,11 @@ RSpec.describe Repository do
let(:latest_tag) { 'v0.0.0' }
before do
- rugged_repo(repository).tags.create(latest_tag, repository.commit.id)
+ repository.add_tag(user, latest_tag, repository.commit.id)
end
after do
- rugged_repo(repository).tags.delete(latest_tag)
+ repository.rm_tag(user, latest_tag)
end
context 'desc' do
@@ -150,16 +150,13 @@ RSpec.describe Repository do
subject { repository.tags_sorted_by('updated_asc').map(&:name) & (tags_to_compare + [annotated_tag_name]) }
before do
- options = { message: 'test tag message\n',
- tagger: { name: 'John Smith', email: 'john@gmail.com' } }
-
- rugged_repo(repository).tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', **options)
+ repository.add_tag(user, annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', 'test tag message\n')
end
it { is_expected.to eq(['v1.0.0', 'v1.1.0', annotated_tag_name]) }
after do
- rugged_repo(repository).tags.delete(annotated_tag_name)
+ repository.rm_tag(user, annotated_tag_name)
end
end
end
diff --git a/spec/serializers/ci/job_entity_spec.rb b/spec/serializers/ci/job_entity_spec.rb
index bd7e2023765..174d9a0aadb 100644
--- a/spec/serializers/ci/job_entity_spec.rb
+++ b/spec/serializers/ci/job_entity_spec.rb
@@ -56,6 +56,10 @@ RSpec.describe Ci::JobEntity do
expect(subject).to include :queued_at
end
+ it 'contains queued_duration' do
+ expect(subject).to include :queued_duration
+ end
+
context 'when job is retryable' do
before do
job.update!(status: :failed)
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
index dae2f63f2f9..2d50c64d63c 100644
--- a/spec/services/git/tag_hooks_service_spec.rb
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -138,7 +138,7 @@ RSpec.describe Git::TagHooksService, :service do
before do
# Create the lightweight tag
- rugged_repo(project.repository).tags.create(tag_name, newrev)
+ project.repository.write_ref("refs/tags/#{tag_name}", newrev)
# Clear tag list cache
project.repository.expire_tags_cache
diff --git a/vendor/gems/ipynbdiff/.gitignore b/vendor/gems/ipynbdiff/.gitignore
new file mode 100644
index 00000000000..4f284c35a42
--- /dev/null
+++ b/vendor/gems/ipynbdiff/.gitignore
@@ -0,0 +1,2 @@
+*.gem
+.bundle
diff --git a/vendor/gems/ipynbdiff/.gitlab-ci.yml b/vendor/gems/ipynbdiff/.gitlab-ci.yml
new file mode 100644
index 00000000000..7b0c9df6cd9
--- /dev/null
+++ b/vendor/gems/ipynbdiff/.gitlab-ci.yml
@@ -0,0 +1,32 @@
+# You can override the included template(s) by including variable overrides
+# SAST customization: https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings
+# Secret Detection customization: https://docs.gitlab.com/ee/user/application_security/secret_detection/#customizing-settings
+# Dependency Scanning customization: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-the-dependency-scanning-settings
+# Note that environment variables can be set in several places
+# See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence
+workflow:
+ rules:
+ - if: $CI_MERGE_REQUEST_ID
+
+.rspec:
+ cache:
+ key: ipynbdiff
+ paths:
+ - vendor/gems/ipynbdiff/vendor/ruby
+ before_script:
+ - cd vendor/gems/ipynbdiff
+ - ruby -v # Print out ruby version for debugging
+ - gem install bundler --no-document # Bundler is not installed with the image
+ - bundle config set --local path 'vendor' # Install dependencies into ./vendor/ruby
+ - bundle config set with 'development'
+ - bundle install -j $(nproc)
+ script:
+ - bundle exec rspec
+
+rspec-2.7:
+ image: "ruby:2.7"
+ extends: .rspec
+
+rspec-3.0:
+ image: "ruby:3.0"
+ extends: .rspec
diff --git a/vendor/gems/ipynbdiff/Gemfile b/vendor/gems/ipynbdiff/Gemfile
new file mode 100644
index 00000000000..7f4f5e950d1
--- /dev/null
+++ b/vendor/gems/ipynbdiff/Gemfile
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+source 'https://rubygems.org'
+
+gemspec
diff --git a/vendor/gems/ipynbdiff/Gemfile.lock b/vendor/gems/ipynbdiff/Gemfile.lock
new file mode 100644
index 00000000000..a5e8e3e4e86
--- /dev/null
+++ b/vendor/gems/ipynbdiff/Gemfile.lock
@@ -0,0 +1,64 @@
+PATH
+ remote: .
+ specs:
+ ipynbdiff (0.4.7)
+ diffy (~> 3.3)
+ json (~> 2.5, >= 2.5.1)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ ast (2.4.2)
+ binding_ninja (0.2.3)
+ coderay (1.1.3)
+ diff-lcs (1.5.0)
+ diffy (3.4.2)
+ json (2.6.2)
+ method_source (1.0.0)
+ parser (3.1.2.0)
+ ast (~> 2.4.1)
+ proc_to_ast (0.1.0)
+ coderay
+ parser
+ unparser
+ pry (0.14.1)
+ coderay (~> 1.1)
+ method_source (~> 1.0)
+ rake (13.0.6)
+ rspec (3.11.0)
+ rspec-core (~> 3.11.0)
+ rspec-expectations (~> 3.11.0)
+ rspec-mocks (~> 3.11.0)
+ rspec-core (3.11.0)
+ rspec-support (~> 3.11.0)
+ rspec-expectations (3.11.0)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.11.0)
+ rspec-mocks (3.11.1)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.11.0)
+ rspec-parameterized (0.5.1)
+ binding_ninja (>= 0.2.3)
+ parser
+ proc_to_ast
+ rspec (>= 2.13, < 4)
+ unparser
+ rspec-support (3.11.0)
+ unparser (0.6.5)
+ diff-lcs (~> 1.3)
+ parser (>= 3.1.0)
+
+PLATFORMS
+ x86_64-darwin-20
+ x86_64-linux
+
+DEPENDENCIES
+ bundler (~> 2.2)
+ ipynbdiff!
+ pry (~> 0.14)
+ rake (~> 13.0)
+ rspec (~> 3.10)
+ rspec-parameterized (~> 0.5.1)
+
+BUNDLED WITH
+ 2.3.16
diff --git a/vendor/gems/ipynbdiff/LICENSE b/vendor/gems/ipynbdiff/LICENSE
new file mode 100644
index 00000000000..e6de2f90864
--- /dev/null
+++ b/vendor/gems/ipynbdiff/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016-2021 GitLab B.V.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/gems/ipynbdiff/README.md b/vendor/gems/ipynbdiff/README.md
new file mode 100644
index 00000000000..f046f678a4d
--- /dev/null
+++ b/vendor/gems/ipynbdiff/README.md
@@ -0,0 +1,56 @@
+# IpynbDiff: Better diff for Jupyter Notebooks
+
+This is a simple diff tool that cleans up Jupyter notebooks, transforming each [notebook](example/1/from.ipynb)
+into a [readable markdown file](example/1/from_html.md), keeping the output of cells, and running the
+diff after. Markdowns are generated using an opinionated Jupyter to Markdown conversion. This means
+that the entire file is readable on the diff.
+
+The result are diffs that are much easier to read:
+
+| Diff | IpynbDiff |
+| ----------------------------------- | ----------------------------------------------------- |
+| [Diff text](example/diff.txt) | [IpynbDiff text](example/ipynbdiff_percent.txt) |
+| ![Diff image](example/img/diff.png) | ![IpynbDiff image](example/img/ipynbdiff_percent.png) |
+
+This started as a port of [ipynbdiff](https://gitlab.com/gitlab-org/incubation-engineering/mlops/poc/ipynbdiff),
+but now has extended functionality although not working as git driver.
+
+## Usage
+
+### Generating diffs
+
+```ruby
+IpynbDiff.diff(from_path, to_path, options)
+```
+
+Options:
+
+```ruby
+@default_transform_options = {
+ preprocess_input: true, # Whether the input should be transformed
+ write_output_to: nil, # Pass a path to save the output to a file
+ format: :text, # These are the formats Diffy accepts https://github.com/samg/diffy
+ sources_are_files: false, # Weather to use the from/to as string or path to a file
+ raise_if_invalid_notebook: false, # Raises an error if the notebooks are invalid, otherwise returns nil
+ transform_options: @default_transform_options, # See below for transform options
+ diff_opts: {
+ include_diff_info: false # These are passed to Diffy https://github.com/samg/diffy
+ }
+}
+```
+
+### Transforming the notebooks
+
+It might be necessary to have the transformed files in addition to the diff.
+
+```ruby
+IpynbDiff.transform(notebook, options)
+```
+
+Options:
+
+```ruby
+@default_transform_options = {
+ include_frontmatter: false, # Whether to include or not the notebook metadata (kernel, language, etc)
+}
+```
diff --git a/vendor/gems/ipynbdiff/ipynbdiff.gemspec b/vendor/gems/ipynbdiff/ipynbdiff.gemspec
new file mode 100644
index 00000000000..8f4dfef8177
--- /dev/null
+++ b/vendor/gems/ipynbdiff/ipynbdiff.gemspec
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+lib = File.expand_path('lib/..', __dir__)
+$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
+
+require 'lib/version'
+
+Gem::Specification.new do |s|
+ s.name = 'ipynbdiff'
+ s.version = IpynbDiff::VERSION
+ s.summary = 'Human Readable diffs for Jupyter Notebooks'
+ s.description = 'Better diff for Jupyter Notebooks by first preprocessing them and removing clutter'
+ s.authors = ['Eduardo Bonet']
+ s.email = 'ebonet@gitlab.com'
+ # Specify which files should be added to the gem when it is released.
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
+ s.files = Dir.glob("lib/**/*.*")
+ s.test_files = Dir.glob("spec/**/*.*")
+ s.homepage =
+ 'https://gitlab.com/gitlab-org/incubation-engineering/mlops/rb-ipynbdiff'
+ s.license = 'MIT'
+
+ s.require_paths = ['lib']
+
+ s.add_runtime_dependency 'diffy', '~> 3.3'
+ s.add_runtime_dependency 'json', '~> 2.5', '>= 2.5.1'
+
+ s.add_development_dependency 'bundler', '~> 2.2'
+ s.add_development_dependency 'pry', '~> 0.14'
+ s.add_development_dependency 'rake', '~> 13.0'
+ s.add_development_dependency 'rspec', '~> 3.10'
+ s.add_development_dependency 'rspec-parameterized', '~> 0.5.1'
+end
diff --git a/vendor/gems/ipynbdiff/lib/diff.rb b/vendor/gems/ipynbdiff/lib/diff.rb
new file mode 100644
index 00000000000..3554ac55d99
--- /dev/null
+++ b/vendor/gems/ipynbdiff/lib/diff.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# Custom differ for Jupyter Notebooks
+module IpynbDiff
+ require 'delegate'
+
+ # The result of a diff object
+ class Diff < SimpleDelegator
+ require 'diffy'
+
+ attr_reader :from, :to
+
+ def initialize(from, to, diffy_opts)
+ super(Diffy::Diff.new(from.as_text, to.as_text, **diffy_opts))
+
+ @from = from
+ @to = to
+ end
+ end
+end
diff --git a/vendor/gems/ipynbdiff/lib/ipynb_symbol_map.rb b/vendor/gems/ipynbdiff/lib/ipynb_symbol_map.rb
new file mode 100644
index 00000000000..33e06aa8d18
--- /dev/null
+++ b/vendor/gems/ipynbdiff/lib/ipynb_symbol_map.rb
@@ -0,0 +1,218 @@
+# frozen_string_literal: true
+
+module IpynbDiff
+ class InvalidTokenError < StandardError
+ end
+
+ # Creates a symbol map for a ipynb file (JSON format)
+ class IpynbSymbolMap
+ class << self
+ def parse(notebook, objects_to_ignore = [])
+ IpynbSymbolMap.new(notebook, objects_to_ignore).parse('')
+ end
+ end
+
+ attr_reader :current_line, :char_idx, :results
+
+ WHITESPACE_CHARS = ["\t", "\r", ' ', "\n"].freeze
+
+ VALUE_STOPPERS = [',', '[', ']', '{', '}', *WHITESPACE_CHARS].freeze
+
+ def initialize(notebook, objects_to_ignore = [])
+ @chars = notebook.chars
+ @current_line = 0
+ @char_idx = 0
+ @results = {}
+ @objects_to_ignore = objects_to_ignore
+ end
+
+ def parse(prefix = '.')
+ raise_if_file_ended
+
+ skip_whitespaces
+
+ if (c = current_char) == '"'
+ parse_string
+ elsif c == '['
+ parse_array(prefix)
+ elsif c == '{'
+ parse_object(prefix)
+ else
+ parse_value
+ end
+
+ results
+ end
+
+ def parse_array(prefix)
+ # [1, 2, {"some": "object"}, [1]]
+
+ i = 0
+
+ current_should_be '['
+
+ loop do
+ raise_if_file_ended
+
+ break if skip_beginning(']')
+
+ new_prefix = "#{prefix}.#{i}"
+
+ add_result(new_prefix, current_line)
+
+ parse(new_prefix)
+
+ i += 1
+ end
+ end
+
+ def parse_object(prefix)
+ # {"name":"value", "another_name": [1, 2, 3]}
+
+ current_should_be '{'
+
+ loop do
+ raise_if_file_ended
+
+ break if skip_beginning('}')
+
+ prop_name = parse_string(return_value: true)
+
+ next_and_skip_whitespaces
+
+ current_should_be ':'
+
+ next_and_skip_whitespaces
+
+ if @objects_to_ignore.include? prop_name
+ skip
+ else
+ new_prefix = "#{prefix}.#{prop_name}"
+
+ add_result(new_prefix, current_line)
+
+ parse(new_prefix)
+ end
+ end
+ end
+
+ def parse_string(return_value: false)
+ current_should_be '"'
+ init_idx = @char_idx
+
+ loop do
+ increment_char_index
+
+ raise_if_file_ended
+
+ if current_char == '"' && !prev_backslash?
+ init_idx += 1
+ break
+ end
+ end
+
+ @chars[init_idx...@char_idx].join if return_value
+ end
+
+ def add_result(key, line_number)
+ @results[key] = line_number
+ end
+
+ def parse_value
+ increment_char_index until raise_if_file_ended || VALUE_STOPPERS.include?(current_char)
+ end
+
+ def skip_whitespaces
+ while WHITESPACE_CHARS.include?(current_char)
+ raise_if_file_ended
+ check_for_new_line
+ increment_char_index
+ end
+ end
+
+ def increment_char_index
+ @char_idx += 1
+ end
+
+ def next_and_skip_whitespaces
+ increment_char_index
+ skip_whitespaces
+ end
+
+ def current_char
+ raise_if_file_ended
+
+ @chars[@char_idx]
+ end
+
+ def prev_backslash?
+ @chars[@char_idx - 1] == '\\' && @chars[@char_idx - 2] != '\\'
+ end
+
+ def current_should_be(another_char)
+ raise InvalidTokenError unless current_char == another_char
+ end
+
+ def check_for_new_line
+ @current_line += 1 if current_char == "\n"
+ end
+
+ def raise_if_file_ended
+ @char_idx >= @chars.size && raise(InvalidTokenError)
+ end
+
+ def skip
+ raise_if_file_ended
+
+ skip_whitespaces
+
+ if (c = current_char) == '"'
+ parse_string
+ elsif c == '['
+ skip_array
+ elsif c == '{'
+ skip_object
+ else
+ parse_value
+ end
+ end
+
+ def skip_array
+ loop do
+ raise_if_file_ended
+
+ break if skip_beginning(']')
+
+ skip
+ end
+ end
+
+ def skip_object
+ loop do
+ raise_if_file_ended
+
+ break if skip_beginning('}')
+
+ parse_string
+
+ next_and_skip_whitespaces
+
+ current_should_be ':'
+
+ next_and_skip_whitespaces
+
+ skip
+ end
+ end
+
+ def skip_beginning(closing_char)
+ check_for_new_line
+
+ next_and_skip_whitespaces
+
+ return true if current_char == closing_char
+
+ next_and_skip_whitespaces if current_char == ','
+ end
+ end
+end
diff --git a/vendor/gems/ipynbdiff/lib/ipynbdiff.rb b/vendor/gems/ipynbdiff/lib/ipynbdiff.rb
new file mode 100644
index 00000000000..1765e434bf9
--- /dev/null
+++ b/vendor/gems/ipynbdiff/lib/ipynbdiff.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# Human Readable Jupyter Diffs
+module IpynbDiff
+ require 'transformer'
+ require 'diff'
+
+ def self.diff(from, to, raise_if_invalid_nb: false, include_frontmatter: false, hide_images: false, diffy_opts: {})
+ transformer = Transformer.new(include_frontmatter: include_frontmatter, hide_images: hide_images)
+
+ Diff.new(transformer.transform(from), transformer.transform(to), diffy_opts)
+ rescue InvalidNotebookError
+ raise if raise_if_invalid_nb
+ end
+
+ def self.transform(notebook, raise_errors: false, include_frontmatter: true, hide_images: false)
+ return unless notebook
+
+ Transformer.new(include_frontmatter: include_frontmatter, hide_images: hide_images).transform(notebook).as_text
+ rescue InvalidNotebookError
+ raise if raise_errors
+ end
+end
diff --git a/vendor/gems/ipynbdiff/lib/output_transformer.rb b/vendor/gems/ipynbdiff/lib/output_transformer.rb
new file mode 100644
index 00000000000..88728df2f17
--- /dev/null
+++ b/vendor/gems/ipynbdiff/lib/output_transformer.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+module IpynbDiff
+ # Transforms Jupyter output data into markdown
+ class OutputTransformer
+ require 'symbolized_markdown_helper'
+ include SymbolizedMarkdownHelper
+
+ HIDDEN_IMAGE_OUTPUT = ' [Hidden Image Output]'
+
+ ORDERED_KEYS = {
+ 'execute_result' => %w[image/png image/svg+xml image/jpeg text/markdown text/latex text/plain],
+ 'display_data' => %w[image/png image/svg+xml image/jpeg text/markdown text/latex],
+ 'stream' => %w[text]
+ }.freeze
+
+ def initialize(hide_images: false)
+ @hide_images = hide_images
+ end
+
+ def transform(output, symbol)
+ transformed = case (output_type = output['output_type'])
+ when 'error'
+ transform_error(output['traceback'], symbol / 'traceback')
+ when 'execute_result', 'display_data'
+ transform_non_error(ORDERED_KEYS[output_type], output['data'], symbol / 'data')
+ when 'stream'
+ transform_element('text', output['text'], symbol)
+ end
+
+ transformed ? decorate_output(transformed, output, symbol) : []
+ end
+
+ def decorate_output(output_rows, output, symbol)
+ [
+ _,
+ _(symbol, %(%%%% Output: #{output['output_type']})),
+ _,
+ *output_rows
+ ]
+ end
+
+ def transform_error(traceback, symbol)
+ traceback.map.with_index do |t, idx|
+ t.split("\n").map do |l|
+ _(symbol / idx, l.gsub(/\[[0-9][0-9;]*m/, '').sub("\u001B", ' ').gsub(/\u001B/, '').rstrip)
+ end
+ end
+ end
+
+ def transform_non_error(accepted_keys, elements, symbol)
+ accepted_keys.filter { |key| elements.key?(key) }.map do |key|
+ transform_element(key, elements[key], symbol)
+ end
+ end
+
+ def transform_element(output_type, output_element, symbol_prefix)
+ new_symbol = symbol_prefix / output_type
+ case output_type
+ when 'image/png', 'image/jpeg'
+ transform_image(output_type + ';base64', output_element, new_symbol)
+ when 'image/svg+xml'
+ transform_image(output_type + ';utf8', output_element, new_symbol)
+ when 'text/markdown', 'text/latex', 'text/plain', 'text'
+ transform_text(output_element, new_symbol)
+ end
+ end
+
+ def transform_image(image_type, image_content, symbol)
+ return _(nil, HIDDEN_IMAGE_OUTPUT) if @hide_images
+
+ lines = image_content.is_a?(Array) ? image_content : [image_content]
+
+ single_line = lines.map(&:strip).join.gsub(/\s+/, ' ')
+
+ _(symbol, " ![](data:#{image_type},#{single_line})")
+ end
+
+ def transform_text(text_content, symbol)
+ symbolize_array(symbol, text_content) { |l| " #{l.rstrip}" }
+ end
+ end
+end
diff --git a/vendor/gems/ipynbdiff/lib/symbolized_markdown_helper.rb b/vendor/gems/ipynbdiff/lib/symbolized_markdown_helper.rb
new file mode 100644
index 00000000000..918666ed899
--- /dev/null
+++ b/vendor/gems/ipynbdiff/lib/symbolized_markdown_helper.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module IpynbDiff
+ # Helper functions
+ module SymbolizedMarkdownHelper
+
+ def _(symbol = nil, content = '')
+ { symbol: symbol, content: content }
+ end
+
+ def symbolize_array(symbol, content, &block)
+ if content.is_a?(Array)
+ content.map.with_index { |l, idx| _(symbol / idx, block.call(l)) }
+ else
+ _(symbol, content)
+ end
+ end
+ end
+
+ # Simple wrapper for a string
+ class JsonSymbol < String
+ def /(other)
+ JsonSymbol.new((other.is_a?(Array) ? [self, *other] : [self, other]).join('.'))
+ end
+ end
+end
diff --git a/vendor/gems/ipynbdiff/lib/transformed_notebook.rb b/vendor/gems/ipynbdiff/lib/transformed_notebook.rb
new file mode 100644
index 00000000000..7a8edf7c22f
--- /dev/null
+++ b/vendor/gems/ipynbdiff/lib/transformed_notebook.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module IpynbDiff
+ # Notebook that was transformed into md, including location of source cells
+ class TransformedNotebook
+ attr_reader :blocks
+
+ def as_text
+ @blocks.map { |b| b[:content] }.join("\n")
+ end
+
+ private
+
+ def initialize(lines = [], symbol_map = {})
+ @blocks = lines.map do |line|
+ { content: line[:content], source_symbol: (symbol = line[:symbol]), source_line: symbol && symbol_map[symbol] }
+ end
+ end
+ end
+end
diff --git a/vendor/gems/ipynbdiff/lib/transformer.rb b/vendor/gems/ipynbdiff/lib/transformer.rb
new file mode 100644
index 00000000000..153d821db27
--- /dev/null
+++ b/vendor/gems/ipynbdiff/lib/transformer.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+module IpynbDiff
+ class InvalidNotebookError < StandardError
+ end
+
+ # Returns a markdown version of the Jupyter Notebook
+ class Transformer
+ require 'json'
+ require 'yaml'
+ require 'output_transformer'
+ require 'symbolized_markdown_helper'
+ require 'ipynb_symbol_map'
+ require 'transformed_notebook'
+ include SymbolizedMarkdownHelper
+
+ @include_frontmatter = true
+ @objects_to_ignore = ['application/javascript', 'application/vnd.holoviews_load.v0+json']
+
+ def initialize(include_frontmatter: true, hide_images: false)
+ @include_frontmatter = include_frontmatter
+ @hide_images = hide_images
+ @output_transformer = OutputTransformer.new(hide_images: hide_images)
+ end
+
+ def validate_notebook(notebook)
+ notebook_json = JSON.parse(notebook)
+
+ return notebook_json if notebook_json.key?('cells')
+
+ raise InvalidNotebookError
+ rescue JSON::ParserError
+ raise InvalidNotebookError
+ end
+
+ def transform(notebook)
+ return TransformedNotebook.new unless notebook
+
+ notebook_json = validate_notebook(notebook)
+ transformed = transform_document(notebook_json)
+ symbol_map = IpynbSymbolMap.parse(notebook)
+
+ TransformedNotebook.new(transformed, symbol_map)
+ end
+
+ def transform_document(notebook)
+ symbol = JsonSymbol.new('.cells')
+
+ transformed_blocks = notebook['cells'].map.with_index do |cell, idx|
+ decorate_cell(transform_cell(cell, notebook, symbol / idx), cell, symbol / idx)
+ end
+
+ transformed_blocks.prepend(transform_metadata(notebook)) if @include_frontmatter
+ transformed_blocks.flatten
+ end
+
+ def decorate_cell(rows, cell, symbol)
+ tags = cell['metadata']&.fetch('tags', [])
+ type = cell['cell_type'] || 'raw'
+
+ [
+ _(symbol, %(%% Cell type:#{type} id:#{cell['id']} tags:#{tags&.join(',')})),
+ _,
+ rows,
+ _
+ ]
+ end
+
+ def transform_cell(cell, notebook, symbol)
+ cell['cell_type'] == 'code' ? transform_code_cell(cell, notebook, symbol) : transform_text_cell(cell, symbol)
+ end
+
+ def transform_code_cell(cell, notebook, symbol)
+ [
+ _(symbol / 'source', %(``` #{notebook.dig('metadata', 'kernelspec', 'language') || ''})),
+ symbolize_array(symbol / 'source', cell['source'], &:rstrip),
+ _(nil, '```'),
+ cell['outputs'].map.with_index do |output, idx|
+ @output_transformer.transform(output, symbol / ['outputs', idx])
+ end
+ ]
+ end
+
+ def transform_text_cell(cell, symbol)
+ symbolize_array(symbol / 'source', cell['source'], &:rstrip)
+ end
+
+ def transform_metadata(notebook_json)
+ as_yaml = {
+ 'jupyter' => {
+ 'kernelspec' => notebook_json['metadata']['kernelspec'],
+ 'language_info' => notebook_json['metadata']['language_info'],
+ 'nbformat' => notebook_json['nbformat'],
+ 'nbformat_minor' => notebook_json['nbformat_minor']
+ }
+ }.to_yaml
+
+ as_yaml.split("\n").map { |l| _(nil, l) }.append(_(nil, '---'), _)
+ end
+ end
+end
diff --git a/vendor/gems/ipynbdiff/lib/version.rb b/vendor/gems/ipynbdiff/lib/version.rb
new file mode 100644
index 00000000000..1451bb4ef32
--- /dev/null
+++ b/vendor/gems/ipynbdiff/lib/version.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+module IpynbDiff
+ VERSION = '0.4.7'
+end
diff --git a/vendor/gems/ipynbdiff/spec/ipynb_symbol_map_spec.rb b/vendor/gems/ipynbdiff/spec/ipynb_symbol_map_spec.rb
new file mode 100644
index 00000000000..a002fc370f5
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/ipynb_symbol_map_spec.rb
@@ -0,0 +1,165 @@
+# frozen_string_literal: true
+
+require 'rspec'
+require 'json'
+require 'rspec-parameterized'
+require 'ipynb_symbol_map'
+
+describe IpynbDiff::IpynbSymbolMap do
+ def res(*cases)
+ cases&.to_h || []
+ end
+
+ describe '#parse_string' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:mapper) { IpynbDiff::IpynbSymbolMap.new(input) }
+
+ where(:input, :result) do
+ # Empty string
+ '""' | ''
+ # Some string with quotes
+ '"he\nll\"o"' | 'he\nll\"o'
+ end
+
+ with_them do
+ it { expect(mapper.parse_string(return_value: true)).to eq(result) }
+ it { expect(mapper.parse_string).to be_nil }
+ it { expect(mapper.results).to be_empty }
+ end
+
+ it 'raises if invalid string' do
+ mapper = IpynbDiff::IpynbSymbolMap.new('"')
+
+ expect { mapper.parse_string }.to raise_error(IpynbDiff::InvalidTokenError)
+ end
+
+ end
+
+ describe '#parse_object' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:mapper) { IpynbDiff::IpynbSymbolMap.new(notebook, objects_to_ignore) }
+
+ before do
+ mapper.parse_object('')
+ end
+
+ where(:notebook, :objects_to_ignore, :result) do
+ # Empty object
+ '{ }' | [] | res
+ # Object with string
+ '{ "hello" : "world" }' | [] | res(['.hello', 0])
+ # Object with boolean
+ '{ "hello" : true }' | [] | res(['.hello', 0])
+ # Object with integer
+ '{ "hello" : 1 }' | [] | res(['.hello', 0])
+ # Object with 2 properties in the same line
+ '{ "hello" : "world" , "my" : "bad" }' | [] | res(['.hello', 0], ['.my', 0])
+ # Object with 2 properties in the different lines line
+ "{ \"hello\" : \"world\" , \n \n \"my\" : \"bad\" }" | [] | res(['.hello', 0], ['.my', 2])
+ # Object with 2 properties, but one is ignored
+ "{ \"hello\" : \"world\" , \n \n \"my\" : \"bad\" }" | ['hello'] | res(['.my', 2])
+ end
+
+ with_them do
+ it { expect(mapper.results).to include(result) }
+ end
+ end
+
+ describe '#parse_array' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:notebook, :result) do
+ # Empty Array
+ '[]' | res
+ # Array with string value
+ '["a"]' | res(['.0', 0])
+ # Array with boolean
+ '[ true ]' | res(['.0', 0])
+ # Array with integer
+ '[ 1 ]' | res(['.0', 0])
+ # Two values on the same line
+ '["a", "b"]' | res(['.0', 0], ['.1', 0])
+ # With line breaks'
+ "[\n \"a\" \n , \n \"b\" ]" | res(['.0', 1], ['.1', 3])
+ end
+
+ let(:mapper) { IpynbDiff::IpynbSymbolMap.new(notebook) }
+
+ before do
+ mapper.parse_array('')
+ end
+
+ with_them do
+ it { expect(mapper.results).to match_array(result) }
+ end
+ end
+
+ describe '#skip_object' do
+ subject { IpynbDiff::IpynbSymbolMap.parse(JSON.pretty_generate(source)) }
+ end
+
+ describe '#parse' do
+
+ let(:objects_to_ignore) { [] }
+
+ subject { IpynbDiff::IpynbSymbolMap.parse(JSON.pretty_generate(source), objects_to_ignore) }
+
+ context 'Empty object' do
+ let(:source) { {} }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'Object with inner object and number' do
+ let(:source) { { obj1: { obj2: 1 } } }
+
+ it { is_expected.to match_array(res(['.obj1', 1], ['.obj1.obj2', 2])) }
+ end
+
+ context 'Object with inner object and number, string and array with object' do
+ let(:source) { { obj1: { obj2: [123, 2, true], obj3: "hel\nlo", obj4: true, obj5: 123, obj6: 'a' } } }
+
+ it do
+ is_expected.to match_array(
+ res(['.obj1', 1],
+ ['.obj1.obj2', 2],
+ ['.obj1.obj2.0', 3],
+ ['.obj1.obj2.1', 4],
+ ['.obj1.obj2.2', 5],
+ ['.obj1.obj3', 7],
+ ['.obj1.obj4', 8],
+ ['.obj1.obj5', 9],
+ ['.obj1.obj6', 10])
+ )
+ end
+ end
+
+ context 'When index is exceeded because of failure' do
+ it 'raises an exception' do
+ source = '{"\\a": "a\""}'
+
+ mapper = IpynbDiff::IpynbSymbolMap.new(source)
+
+ expect(mapper).to receive(:prev_backslash?).at_least(1).time.and_return(false)
+
+ expect { mapper.parse('') }.to raise_error(IpynbDiff::InvalidTokenError)
+ end
+ end
+
+ context 'Object with inner object and number, string and array with object' do
+ let(:source) { { obj1: { obj2: [123, 2, true], obj3: "hel\nlo", obj4: true, obj5: 123, obj6: { obj7: 'a' } } } }
+ let(:objects_to_ignore) { %w(obj2 obj6) }
+ it do
+ is_expected.to match_array(
+ res(['.obj1', 1],
+ ['.obj1.obj3', 7],
+ ['.obj1.obj4', 8],
+ ['.obj1.obj5', 9],
+ )
+ )
+ end
+ end
+ end
+end
diff --git a/vendor/gems/ipynbdiff/spec/ipynbdiff_spec.rb b/vendor/gems/ipynbdiff/spec/ipynbdiff_spec.rb
new file mode 100644
index 00000000000..1c2a2188edf
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/ipynbdiff_spec.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require 'ipynbdiff'
+require 'rspec'
+require 'rspec-parameterized'
+
+BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'testdata')
+
+describe IpynbDiff do
+ def diff_signs(diff)
+ diff.to_s(:text).scan(/.*\n/).map { |l| l[0] }.join('')
+ end
+
+ describe 'diff' do
+ let(:from_path) { File.join(BASE_PATH, 'from.ipynb') }
+ let(:to_path) { File.join(BASE_PATH,'to.ipynb') }
+ let(:from) { File.read(from_path) }
+ let(:to) { File.read(to_path) }
+ let(:include_frontmatter) { false }
+ let(:hide_images) { false }
+
+ subject { IpynbDiff.diff(from, to, include_frontmatter: include_frontmatter, hide_images: hide_images) }
+
+ context 'if preprocessing is active' do
+ it 'html tables are stripped' do
+ is_expected.to_not include('<td>')
+ end
+ end
+
+ context 'when to is nil' do
+ let(:to) { nil }
+ let(:from_path) { File.join(BASE_PATH, 'only_md', 'input.ipynb') }
+
+ it 'all lines are removals' do
+ expect(diff_signs(subject)).to eq('-----')
+ end
+ end
+
+ context 'when to is nil' do
+ let(:from) { nil }
+ let(:to_path) { File.join(BASE_PATH, 'only_md', 'input.ipynb') }
+
+ it 'all lines are additions' do
+ expect(diff_signs(subject)).to eq('+++++')
+ end
+ end
+
+ context 'When include_frontmatter is true' do
+ let(:include_frontmatter) { true }
+
+ it 'should show changes metadata in the metadata' do
+ expect(subject.to_s(:text)).to include('+ display_name: New Python 3 (ipykernel)')
+ end
+ end
+
+ context 'When hide_images is true' do
+ let(:hide_images) { true }
+
+ it 'hides images' do
+ expect(subject.to_s(:text)).to include(' [Hidden Image Output]')
+ end
+ end
+
+ context 'When include_frontmatter is false' do
+ it 'should drop metadata from the diff' do
+ expect(subject.to_s(:text)).to_not include('+ display_name: New Python 3 (ipykernel)')
+ end
+ end
+
+ context 'when either notebook can not be processed' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:ctx, :from, :to) do
+ 'because from is invalid' | 'a' | nil
+ 'because from does not have the cell tag' | '{"metadata":[]}' | nil
+ 'because to is invalid' | nil | 'a'
+ 'because to does not have the cell tag' | nil | '{"metadata":[]}'
+ end
+
+ with_them do
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
+ describe 'transform' do
+ [nil, 'a', '{"metadata":[]}'].each do |invalid_nb|
+ context "when json is invalid (#{invalid_nb || 'nil'})" do
+ it 'is nil' do
+ expect(IpynbDiff.transform(invalid_nb)).to be_nil
+ end
+ end
+ end
+
+ context 'options' do
+ let(:include_frontmatter) { false }
+ let(:hide_images) { false }
+
+ subject do
+ IpynbDiff.transform(File.read(File.join(BASE_PATH, 'from.ipynb')),
+ include_frontmatter: include_frontmatter,
+ hide_images: hide_images)
+ end
+
+ context 'include_frontmatter is false' do
+ it { is_expected.to_not include('display_name: Python 3 (ipykernel)') }
+ end
+
+ context 'include_frontmatter is true' do
+ let(:include_frontmatter) { true }
+
+ it { is_expected.to include('display_name: Python 3 (ipykernel)') }
+ end
+
+ context 'hide_images is false' do
+ it { is_expected.not_to include('[Hidden Image Output]') }
+ end
+
+ context 'hide_images is true' do
+ let(:hide_images) { true }
+
+ it { is_expected.to include(' [Hidden Image Output]') }
+ end
+ end
+ end
+end
diff --git a/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected.md b/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected.md
new file mode 100644
index 00000000000..299e286c679
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected.md
@@ -0,0 +1,7 @@
+%% Cell type:markdown id: tags:
+
+\
+
+%% Cell type:markdown id: tags:
+
+a
diff --git a/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected_symbols.txt
new file mode 100644
index 00000000000..6fa29ae28de
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/expected_symbols.txt
@@ -0,0 +1,7 @@
+.cells.0
+
+.cells.0.source.0
+
+.cells.1
+
+.cells.1.source.0
diff --git a/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/input.ipynb
new file mode 100644
index 00000000000..0714044e3ae
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/backslash_as_last_char/input.ipynb
@@ -0,0 +1,16 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "source": [
+ "\\"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "a"
+ ]
+ }
+ ]
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md
new file mode 100644
index 00000000000..5be645de9c9
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md
@@ -0,0 +1,16 @@
+%% Cell type:code id:5 tags:
+
+``` python
+# A cell that has an error
+y = sin(x)
+```
+
+%%%% Output: error
+
+ ---------------------------------------------------------------------------
+ NameError Traceback (most recent call last)
+ /var/folders/cq/l637k4x13gx6y9p_gfs4c_gc0000gn/T/ipykernel_72857/3962062127.py in <module>
+ 1 # A cell that has an error
+ ----> 2 y = sin(x)
+
+ NameError: name 'sin' is not defined
diff --git a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt
new file mode 100644
index 00000000000..75e35d123d0
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt
@@ -0,0 +1,16 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+.cells.0.source.1
+
+
+.cells.0.outputs.0
+
+.cells.0.outputs.0.traceback.0
+.cells.0.outputs.0.traceback.1
+.cells.0.outputs.0.traceback.2
+.cells.0.outputs.0.traceback.2
+.cells.0.outputs.0.traceback.2
+.cells.0.outputs.0.traceback.2
+.cells.0.outputs.0.traceback.3
diff --git a/vendor/gems/ipynbdiff/spec/testdata/error_output/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/error_output/input.ipynb
new file mode 100644
index 00000000000..45ee81a0e2d
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/error_output/input.ipynb
@@ -0,0 +1,32 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "5",
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "NameError",
+ "evalue": "name 'sin' is not defined",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m/var/folders/cq/l637k4x13gx6y9p_gfs4c_gc0000gn/T/ipykernel_72857/3962062127.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m# A cell that has an error\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0my\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0msin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31mNameError\u001b[0m: name 'sin' is not defined"
+ ]
+ }
+ ],
+ "source": [
+ "# A cell that has an error\n",
+ "y = sin(x)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "language": "python"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/from.ipynb b/vendor/gems/ipynbdiff/spec/testdata/from.ipynb
new file mode 100644
index 00000000000..a731c9bfffd
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/from.ipynb
@@ -0,0 +1,198 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0aac5da7-745c-4eda-847a-3d0d07a1bb9b",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "# This is a markdown cell\n",
+ "\n",
+ "This paragraph has\n",
+ "With\n",
+ "Many\n",
+ "Lines. How we will he handle MR notes?\n",
+ "\n",
+ "But I can add another paragraph"
+ ]
+ },
+ {
+ "cell_type": "raw",
+ "id": "faecea5b-de0a-49fa-9a3a-61c2add652da",
+ "metadata": {},
+ "source": [
+ "This is a raw cell\n",
+ "With\n",
+ "Multiple lines"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "893ca2c0-ab75-4276-9dad-be1c40e16e8a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "0d707fb5-226f-46d6-80bd-489ebfb8905c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "np.random.seed(42)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "35467fcf-28b1-4c7b-bb09-4cb192c35293",
+ "metadata": {
+ "tags": [
+ "senoid"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[<matplotlib.lines.Line2D at 0x123e39370>]"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "some_invalid_base64_image_here\n",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "x = np.linspace(0, 4*np.pi,50)\n",
+ "y = np.sin(x)\n",
+ "\n",
+ "plt.plot(x, y)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "dc1178cd-c46d-4da3-9ab5-08f000699884",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df = pd.DataFrame({\"x\": x, \"y\": y})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "6e749b4f-b409-4700-870f-f68c39462490",
+ "metadata": {
+ "tags": [
+ "some-table"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "<div>\n",
+ "<style scoped>\n",
+ " .dataframe tbody tr th:only-of-type {\n",
+ " vertical-align: middle;\n",
+ " }\n",
+ "\n",
+ " .dataframe tbody tr th {\n",
+ " vertical-align: top;\n",
+ " }\n",
+ "\n",
+ " .dataframe thead th {\n",
+ " text-align: right;\n",
+ " }\n",
+ "</style>\n",
+ "<table border=\"1\" class=\"dataframe\">\n",
+ " <thead>\n",
+ " <tr style=\"text-align: right;\">\n",
+ " <th></th>\n",
+ " <th>x</th>\n",
+ " <th>y</th>\n",
+ " </tr>\n",
+ " </thead>\n",
+ " <tbody>\n",
+ " <tr>\n",
+ " <th>0</th>\n",
+ " <td>0.000000</td>\n",
+ " <td>0.000000</td>\n",
+ " </tr>\n",
+ " <tr>\n",
+ " <th>1</th>\n",
+ " <td>0.256457</td>\n",
+ " <td>0.253655</td>\n",
+ " </tr>\n",
+ " </tbody>\n",
+ "</table>\n",
+ "</div>"
+ ],
+ "text/plain": [
+ " x y\n",
+ "0 0.000000 0.000000\n",
+ "1 0.256457 0.253655"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df[:2]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0ddef5ef-94a3-4afd-9c70-ddee9694f512",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.7"
+ },
+ "toc-showtags": true
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md b/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md
new file mode 100644
index 00000000000..89a812740a6
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md
@@ -0,0 +1,12 @@
+%% Cell type:code id:5 tags:senoid
+
+``` python
+```
+
+%%%% Output: display_data
+
+ [Hidden Image Output]
+
+%%%% Output: display_data
+
+ [Hidden Image Output]
diff --git a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt
new file mode 100644
index 00000000000..b94e9538f58
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt
@@ -0,0 +1,12 @@
+.cells.0
+
+.cells.0.source
+
+
+.cells.0.outputs.0
+
+
+
+.cells.0.outputs.1
+
+
diff --git a/vendor/gems/ipynbdiff/spec/testdata/hide_images/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/hide_images/input.ipynb
new file mode 100644
index 00000000000..dab0e5bb9cf
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/hide_images/input.ipynb
@@ -0,0 +1,45 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "5",
+ "metadata": {
+ "tags": [
+ "senoid"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "this_is_an_invalid_hash_for_testing_purposes\n",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><circle cx=\"50\" cy=\"50\" r=\"50\"/></svg>",
+ "text/plain": [
+ "<IPython.core.display.SVG object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "language": "python"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md
new file mode 100644
index 00000000000..456224f3aff
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md
@@ -0,0 +1,11 @@
+%% Cell type:code id:5 tags:some-table
+
+``` python
+df[:2]
+```
+
+%%%% Output: execute_result
+
+ x y
+ 0 0.000000 0.000000
+ 1 0.256457 0.507309
diff --git a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt
new file mode 100644
index 00000000000..fa9d412c6dc
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt
@@ -0,0 +1,11 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+
+
+.cells.0.outputs.0
+
+.cells.0.outputs.0.data.text/plain.0
+.cells.0.outputs.0.data.text/plain.1
+.cells.0.outputs.0.data.text/plain.2
diff --git a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/input.ipynb
new file mode 100644
index 00000000000..26117a78934
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/input.ipynb
@@ -0,0 +1,74 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "5",
+ "metadata": {
+ "tags": [
+ "some-table"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "<div>\n",
+ "<style scoped>\n",
+ " .dataframe tbody tr th:only-of-type {\n",
+ " vertical-align: middle;\n",
+ " }\n",
+ "\n",
+ " .dataframe tbody tr th {\n",
+ " vertical-align: top;\n",
+ " }\n",
+ "\n",
+ " .dataframe thead th {\n",
+ " text-align: right;\n",
+ " }\n",
+ "</style>\n",
+ "<table border=\"1\" class=\"dataframe\">\n",
+ " <thead>\n",
+ " <tr style=\"text-align: right;\">\n",
+ " <th></th>\n",
+ " <th>x</th>\n",
+ " <th>y</th>\n",
+ " </tr>\n",
+ " </thead>\n",
+ " <tbody>\n",
+ " <tr>\n",
+ " <th>0</th>\n",
+ " <td>0.000000</td>\n",
+ " <td>0.000000</td>\n",
+ " </tr>\n",
+ " <tr>\n",
+ " <th>1</th>\n",
+ " <td>0.256457</td>\n",
+ " <td>0.507309</td>\n",
+ " </tr>\n",
+ " </tbody>\n",
+ "</table>\n",
+ "</div>"
+ ],
+ "text/plain": [
+ " x y\n",
+ "0 0.000000 0.000000\n",
+ "1 0.256457 0.507309"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df[:2]"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "language": "python"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md
new file mode 100644
index 00000000000..add84ed26a0
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md
@@ -0,0 +1,10 @@
+%% Cell type:code id:5 tags:
+
+``` python
+from IPython.display import display, Math
+display(Math(r'Dims: {}x{}m \\ Area: {}m^2 \\ Volume: {}m^3'.format(1, round(2,2), 3, 4)))
+```
+
+%%%% Output: display_data
+
+ $\displaystyle Dims: 1x2m \\ Area: 3m^2 \\ Volume: 4m^3$
diff --git a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt
new file mode 100644
index 00000000000..9407e6db702
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt
@@ -0,0 +1,10 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+.cells.0.source.1
+
+
+.cells.0.outputs.0
+
+.cells.0.outputs.0.data.text/latex.0
diff --git a/vendor/gems/ipynbdiff/spec/testdata/latex_output/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/latex_output/input.ipynb
new file mode 100644
index 00000000000..f8ff3e72beb
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/latex_output/input.ipynb
@@ -0,0 +1,34 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "5",
+ "outputs": [
+ {
+ "data": {
+ "text/latex": [
+ "$\\displaystyle Dims: 1x2m \\\\ Area: 3m^2 \\\\ Volume: 4m^3$"
+ ],
+ "text/plain": [
+ "<IPython.core.display.Math object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from IPython.display import display, Math\n",
+ "display(Math(r'Dims: {}x{}m \\\\ Area: {}m^2 \\\\ Volume: {}m^3'.format(1, round(2,2), 3, 4)))"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md
new file mode 100644
index 00000000000..4a880d8ce18
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md
@@ -0,0 +1,9 @@
+%% Cell type:code id:5 tags:
+
+```
+Some Image
+```
+
+%%%% Output: display_data
+
+ ![](data:image/png;base64,this_is_an_invalid_hash_for_testing_purposes)
diff --git a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt
new file mode 100644
index 00000000000..26e11781ec1
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt
@@ -0,0 +1,9 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+
+
+.cells.0.outputs.0
+
+.cells.0.outputs.0.data.image/png
diff --git a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/input.ipynb
new file mode 100644
index 00000000000..4d19a504553
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/input.ipynb
@@ -0,0 +1,25 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "id": "5",
+ "metadata": {
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": [
+ "this_is_an_invalid_hash_for_testing_purposes"
+ ]
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "Some Image"
+ ]
+ }
+ ],
+ "metadata": {
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected.md b/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected.md
new file mode 100644
index 00000000000..b7c09c51fb8
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected.md
@@ -0,0 +1,19 @@
+---
+jupyter:
+ kernelspec:
+ display_name: Python 3 (ipykernel)
+ language: python
+ name: python3
+ language_info:
+ codemirror_mode:
+ name: ipython
+ version: 3
+ file_extension: ".py"
+ mimetype: text/x-python
+ name: python
+ nbconvert_exporter: python
+ pygments_lexer: ipython3
+ version: 3.9.7
+ nbformat: 4
+ nbformat_minor: 5
+---
diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected_symbols.txt
new file mode 100644
index 00000000000..a60f3032882
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/no_cells/expected_symbols.txt
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/no_cells/input.ipynb
new file mode 100644
index 00000000000..c2ba0ebf50a
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/no_cells/input.ipynb
@@ -0,0 +1,25 @@
+{
+ "cells": [],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.7"
+ },
+ "toc-showtags": true
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected.md b/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected.md
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected.md
diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected_symbols.txt
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/expected_symbols.txt
diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/input.ipynb
new file mode 100644
index 00000000000..c2ba0ebf50a
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/no_cells_no_metadata/input.ipynb
@@ -0,0 +1,25 @@
+{
+ "cells": [],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.7"
+ },
+ "toc-showtags": true
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected.md b/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected.md
new file mode 100644
index 00000000000..d9d72bf8f76
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected.md
@@ -0,0 +1,13 @@
+%% Cell type:markdown id:1 tags:
+
+# A
+
+B
+
+%% Cell type:code id:3 tags:
+
+``` python
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+```
diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected_symbols.txt
new file mode 100644
index 00000000000..a7000494a1b
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/expected_symbols.txt
@@ -0,0 +1,13 @@
+.cells.0
+
+.cells.0.source.0
+.cells.0.source.1
+.cells.0.source.2
+
+.cells.1
+
+.cells.1.source
+.cells.1.source.0
+.cells.1.source.1
+.cells.1.source.2
+
diff --git a/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/input.ipynb
new file mode 100644
index 00000000000..62060124a2a
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/no_metadata_on_cell/input.ipynb
@@ -0,0 +1,29 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "source": [
+ "# A\n",
+ "\n",
+ "B"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "3",
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "language": "python"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code/expected.md b/vendor/gems/ipynbdiff/spec/testdata/only_code/expected.md
new file mode 100644
index 00000000000..124b8217a6a
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code/expected.md
@@ -0,0 +1,7 @@
+%% Cell type:code id:3 tags:
+
+``` python
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+```
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/only_code/expected_symbols.txt
new file mode 100644
index 00000000000..59b11103195
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code/expected_symbols.txt
@@ -0,0 +1,7 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+.cells.0.source.1
+.cells.0.source.2
+
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/only_code/input.ipynb
new file mode 100644
index 00000000000..a93108dccb8
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code/input.ipynb
@@ -0,0 +1,21 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "3",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "language": "python"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected.md b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected.md
new file mode 100644
index 00000000000..9100045e0f5
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected.md
@@ -0,0 +1,5 @@
+%% Cell type:code id:3 tags:
+
+```
+
+```
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected_symbols.txt
new file mode 100644
index 00000000000..5f9ad320b8c
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/expected_symbols.txt
@@ -0,0 +1,5 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source
+
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/input.ipynb
new file mode 100644
index 00000000000..c3ff71057a6
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_kernelspec/input.ipynb
@@ -0,0 +1,12 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "3",
+ "source": "",
+ "outputs": []
+ }
+ ],
+ "metadata": {}
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected.md b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected.md
new file mode 100644
index 00000000000..9100045e0f5
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected.md
@@ -0,0 +1,5 @@
+%% Cell type:code id:3 tags:
+
+```
+
+```
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected_symbols.txt
new file mode 100644
index 00000000000..5f9ad320b8c
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/expected_symbols.txt
@@ -0,0 +1,5 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source
+
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/input.ipynb
new file mode 100644
index 00000000000..fb16b106cbe
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_language/input.ipynb
@@ -0,0 +1,14 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "3",
+ "source": "",
+ "outputs": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {}
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected.md b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected.md
new file mode 100644
index 00000000000..9100045e0f5
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected.md
@@ -0,0 +1,5 @@
+%% Cell type:code id:3 tags:
+
+```
+
+```
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected_symbols.txt
new file mode 100644
index 00000000000..5f9ad320b8c
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/expected_symbols.txt
@@ -0,0 +1,5 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source
+
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/input.ipynb
new file mode 100644
index 00000000000..364c080168b
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_code_no_metadata/input.ipynb
@@ -0,0 +1,11 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "3",
+ "source": "",
+ "outputs": []
+ }
+ ]
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_md/expected.md b/vendor/gems/ipynbdiff/spec/testdata/only_md/expected.md
new file mode 100644
index 00000000000..bdf4db5aea5
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_md/expected.md
@@ -0,0 +1,5 @@
+%% Cell type:markdown id:1 tags:hello,world
+
+# A
+
+B
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_md/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/only_md/expected_symbols.txt
new file mode 100644
index 00000000000..d3d6d526fc3
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_md/expected_symbols.txt
@@ -0,0 +1,5 @@
+.cells.0
+
+.cells.0.source.0
+.cells.0.source.1
+.cells.0.source.2
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_md/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/only_md/input.ipynb
new file mode 100644
index 00000000000..9d6b550af31
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_md/input.ipynb
@@ -0,0 +1,21 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {
+ "tags": [
+ "hello",
+ "world"
+ ]
+ },
+ "source": [
+ "# A\n",
+ "\n",
+ "B"
+ ]
+ }
+ ],
+ "metadata": {
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected.md b/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected.md
new file mode 100644
index 00000000000..91c476e843b
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected.md
@@ -0,0 +1,4 @@
+%% Cell type:raw id:2 tags:
+
+A
+B
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected_symbols.txt
new file mode 100644
index 00000000000..bceaf355c2f
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_raw/expected_symbols.txt
@@ -0,0 +1,4 @@
+.cells.0
+
+.cells.0.source.0
+.cells.0.source.1
diff --git a/vendor/gems/ipynbdiff/spec/testdata/only_raw/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/only_raw/input.ipynb
new file mode 100644
index 00000000000..750e1bba615
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/only_raw/input.ipynb
@@ -0,0 +1,15 @@
+{
+ "cells": [
+ {
+ "cell_type": "raw",
+ "id": "2",
+ "metadata": {},
+ "source": [
+ "A\n",
+ "B"
+ ]
+ }
+ ],
+ "metadata": {
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md b/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md
new file mode 100644
index 00000000000..ecb0029f256
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md
@@ -0,0 +1,70 @@
+%% Cell type:markdown id:0aac5da7-745c-4eda-847a-3d0d07a1bb9b tags:
+
+# This is a markdown cell
+
+This paragraph has
+With
+Many
+Lines. How we will he handle MR notes?
+
+But I can add another paragraph
+
+%% Cell type:raw id:faecea5b-de0a-49fa-9a3a-61c2add652da tags:
+
+This is a raw cell
+With
+Multiple lines
+
+%% Cell type:code id:893ca2c0-ab75-4276-9dad-be1c40e16e8a tags:
+
+``` python
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+```
+
+%% Cell type:code id:0d707fb5-226f-46d6-80bd-489ebfb8905c tags:
+
+``` python
+np.random.seed(42)
+```
+
+%% Cell type:code id:35467fcf-28b1-4c7b-bb09-4cb192c35293 tags:senoid
+
+``` python
+x = np.linspace(0, 4*np.pi,50)
+y = np.sin(x)
+
+plt.plot(x, y)
+```
+
+%%%% Output: execute_result
+
+ [<matplotlib.lines.Line2D at 0x123e39370>]
+
+%%%% Output: display_data
+
+ ![](data:image/png;base64,some_invalid_base64_image_here)
+
+%% Cell type:code id:dc1178cd-c46d-4da3-9ab5-08f000699884 tags:
+
+``` python
+df = pd.DataFrame({"x": x, "y": y})
+```
+
+%% Cell type:code id:6e749b4f-b409-4700-870f-f68c39462490 tags:some-table
+
+``` python
+df[:2]
+```
+
+%%%% Output: execute_result
+
+ x y
+ 0 0.000000 0.000000
+ 1 0.256457 0.253655
+
+%% Cell type:code id:0ddef5ef-94a3-4afd-9c70-ddee9694f512 tags:
+
+``` python
+```
diff --git a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt
new file mode 100644
index 00000000000..ab70e7bc908
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt
@@ -0,0 +1,70 @@
+.cells.0
+
+.cells.0.source.0
+.cells.0.source.1
+.cells.0.source.2
+.cells.0.source.3
+.cells.0.source.4
+.cells.0.source.5
+.cells.0.source.6
+.cells.0.source.7
+
+.cells.1
+
+.cells.1.source.0
+.cells.1.source.1
+.cells.1.source.2
+
+.cells.2
+
+.cells.2.source
+.cells.2.source.0
+.cells.2.source.1
+.cells.2.source.2
+
+
+.cells.3
+
+.cells.3.source
+.cells.3.source.0
+
+
+.cells.4
+
+.cells.4.source
+.cells.4.source.0
+.cells.4.source.1
+.cells.4.source.2
+.cells.4.source.3
+
+
+.cells.4.outputs.0
+
+.cells.4.outputs.0.data.text/plain.0
+
+.cells.4.outputs.1
+
+.cells.4.outputs.1.data.image/png
+
+.cells.5
+
+.cells.5.source
+.cells.5.source.0
+
+
+.cells.6
+
+.cells.6.source
+.cells.6.source.0
+
+
+.cells.6.outputs.0
+
+.cells.6.outputs.0.data.text/plain.0
+.cells.6.outputs.0.data.text/plain.1
+.cells.6.outputs.0.data.text/plain.2
+
+.cells.7
+
+.cells.7.source
+
diff --git a/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected.md b/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected.md
new file mode 100644
index 00000000000..392a5048f59
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected.md
@@ -0,0 +1,3 @@
+%% Cell type:markdown id:1 tags:hello,world
+
+A
diff --git a/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected_symbols.txt
new file mode 100644
index 00000000000..86a7f6b3960
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/single_line_md/expected_symbols.txt
@@ -0,0 +1,3 @@
+.cells.0
+
+.cells.0.source
diff --git a/vendor/gems/ipynbdiff/spec/testdata/single_line_md/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/single_line_md/input.ipynb
new file mode 100644
index 00000000000..5ebd41adbfa
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/single_line_md/input.ipynb
@@ -0,0 +1,17 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "1",
+ "metadata": {
+ "tags": [
+ "hello",
+ "world"
+ ]
+ },
+ "source": "A"
+ }
+ ],
+ "metadata": {
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md b/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md
new file mode 100644
index 00000000000..fb862cbb636
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md
@@ -0,0 +1,9 @@
+%% Cell type:code id:123 tags:
+
+``` python
+print("G'bye")
+```
+
+%%%% Output: stream
+
+ G'bye
diff --git a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt
new file mode 100644
index 00000000000..ed4a8a075d3
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt
@@ -0,0 +1,9 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+
+
+.cells.0.outputs.0
+
+.cells.0.outputs.0.text.0
diff --git a/vendor/gems/ipynbdiff/spec/testdata/stream_text/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/stream_text/input.ipynb
new file mode 100644
index 00000000000..14601fe35e5
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/stream_text/input.ipynb
@@ -0,0 +1,27 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "123",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "G'bye\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"G'bye\")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "language": "python"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/svg/expected.md b/vendor/gems/ipynbdiff/spec/testdata/svg/expected.md
new file mode 100644
index 00000000000..37269446f5a
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/svg/expected.md
@@ -0,0 +1,19 @@
+%% Cell type:code id:5 tags:
+
+``` python
+from IPython.display import SVG, display
+
+svg = """<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="50" cy="50" r="50"/>
+</svg>"""
+
+display(SVG(svg))
+```
+
+%%%% Output: display_data
+
+ ![](data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="50"/></svg>)
+
+%%%% Output: display_data
+
+ ![](data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="50"/></svg>)
diff --git a/vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt
new file mode 100644
index 00000000000..dd2e412302d
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt
@@ -0,0 +1,19 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+.cells.0.source.1
+.cells.0.source.2
+.cells.0.source.3
+.cells.0.source.4
+.cells.0.source.5
+.cells.0.source.6
+
+
+.cells.0.outputs.0
+
+.cells.0.outputs.0.data.image/svg+xml
+
+.cells.0.outputs.1
+
+.cells.0.outputs.1.data.image/svg+xml
diff --git a/vendor/gems/ipynbdiff/spec/testdata/svg/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/svg/input.ipynb
new file mode 100644
index 00000000000..a02d01f7bf2
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/svg/input.ipynb
@@ -0,0 +1,66 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "5",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">\n",
+ " <circle cx=\"50\" cy=\"50\" r=\"50\"/>\n",
+ "</svg>"
+ ],
+ "text/plain": [
+ "<IPython.core.display.SVG object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "image/svg+xml": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><circle cx=\"50\" cy=\"50\" r=\"50\"/></svg>",
+ "text/plain": [
+ "<IPython.core.display.SVG object>"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from IPython.display import SVG, display\n",
+ "\n",
+ "svg = \"\"\"<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">\n",
+ " <circle cx=\"50\" cy=\"50\" r=\"50\"/>\n",
+ "</svg>\"\"\"\n",
+ "\n",
+ "display(SVG(svg))"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md
new file mode 100644
index 00000000000..924f4939f54
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md
@@ -0,0 +1,9 @@
+%% Cell type:code id:5 tags:senoid
+
+``` python
+plt.plot(x, y)
+```
+
+%%%% Output: execute_result
+
+ [<matplotlib.lines.Line2D at 0x12a4e43d0>]
diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt
new file mode 100644
index 00000000000..179b30098a1
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt
@@ -0,0 +1,9 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+
+
+.cells.0.outputs.0
+
+.cells.0.outputs.0.data.text/plain.0
diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_output/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/text_output/input.ipynb
new file mode 100644
index 00000000000..b1b387bb99d
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/text_output/input.ipynb
@@ -0,0 +1,31 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "id": "5",
+ "metadata": {
+ "tags": [
+ "senoid"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[<matplotlib.lines.Line2D at 0x12a4e43d0>]"
+ ]
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "plt.plot(x, y)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "language": "python"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md
new file mode 100644
index 00000000000..b1dda235951
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md
@@ -0,0 +1,16 @@
+%% Cell type:code id:5 tags:senoid
+
+``` python
+x = np.linspace(0, 4*np.pi,50)
+y = 2 * np.sin(x)
+
+plt.plot(x, y)
+```
+
+%%%% Output: execute_result
+
+ [<matplotlib.lines.Line2D at 0x12a4e43d0>]
+
+%%%% Output: display_data
+
+ ![](data:image/png;base64,this_is_an_invalid_hash_for_testing_purposes)
diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt
new file mode 100644
index 00000000000..5a86e4daa67
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt
@@ -0,0 +1,16 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+.cells.0.source.1
+.cells.0.source.2
+.cells.0.source.3
+
+
+.cells.0.outputs.0
+
+.cells.0.outputs.0.data.text/plain.0
+
+.cells.0.outputs.1
+
+.cells.0.outputs.1.data.image/png
diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/input.ipynb
new file mode 100644
index 00000000000..3728b129d26
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/input.ipynb
@@ -0,0 +1,49 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "5",
+ "metadata": {
+ "tags": [
+ "senoid"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[<matplotlib.lines.Line2D at 0x12a4e43d0>]"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "this_is_an_invalid_hash_for_testing_purposes\n",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "x = np.linspace(0, 4*np.pi,50)\n",
+ "y = 2 * np.sin(x)\n",
+ "\n",
+ "plt.plot(x, y)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "language": "python"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/to.ipynb b/vendor/gems/ipynbdiff/spec/testdata/to.ipynb
new file mode 100644
index 00000000000..99b51f3b857
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/to.ipynb
@@ -0,0 +1,200 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0aac5da7-745c-4eda-847a-3d0d07a1bb9b",
+ "metadata": {
+ "tags": []
+ },
+ "source": [
+ "# This is a markdown cell\n",
+ "\n",
+ "This paragraph has\n",
+ "With\n",
+ "Many\n",
+ "Lines. How we will he handle MR notes?\n",
+ "\n",
+ "But I can add another paragraph\n",
+ "\n",
+ "Another paragraph added"
+ ]
+ },
+ {
+ "cell_type": "raw",
+ "id": "faecea5b-de0a-49fa-9a3a-61c2add652da",
+ "metadata": {},
+ "source": [
+ "This is a raw cell\n",
+ "With\n",
+ "Multiple lines"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "893ca2c0-ab75-4276-9dad-be1c40e16e8a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "0d707fb5-226f-46d6-80bd-489ebfb8905c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "np.random.seed(42)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "35467fcf-28b1-4c7b-bb09-4cb192c35293",
+ "metadata": {
+ "tags": [
+ "senoid"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[<matplotlib.lines.Line2D at 0x12a4e43d0>]"
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ },
+ {
+ "data": {
+ "image/png": "another_invalid_base64_image_here\n",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "x = np.linspace(0, 4*np.pi,50)\n",
+ "y = 2 * np.sin(x)\n",
+ "\n",
+ "plt.plot(x, y)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "dc1178cd-c46d-4da3-9ab5-08f000699884",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df = pd.DataFrame({\"x\": x, \"y\": y})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "6e749b4f-b409-4700-870f-f68c39462490",
+ "metadata": {
+ "tags": [
+ "some-table"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "<div>\n",
+ "<style scoped>\n",
+ " .dataframe tbody tr th:only-of-type {\n",
+ " vertical-align: middle;\n",
+ " }\n",
+ "\n",
+ " .dataframe tbody tr th {\n",
+ " vertical-align: top;\n",
+ " }\n",
+ "\n",
+ " .dataframe thead th {\n",
+ " text-align: right;\n",
+ " }\n",
+ "</style>\n",
+ "<table border=\"1\" class=\"dataframe\">\n",
+ " <thead>\n",
+ " <tr style=\"text-align: right;\">\n",
+ " <th></th>\n",
+ " <th>x</th>\n",
+ " <th>y</th>\n",
+ " </tr>\n",
+ " </thead>\n",
+ " <tbody>\n",
+ " <tr>\n",
+ " <th>0</th>\n",
+ " <td>0.000000</td>\n",
+ " <td>0.000000</td>\n",
+ " </tr>\n",
+ " <tr>\n",
+ " <th>1</th>\n",
+ " <td>0.256457</td>\n",
+ " <td>0.507309</td>\n",
+ " </tr>\n",
+ " </tbody>\n",
+ "</table>\n",
+ "</div>"
+ ],
+ "text/plain": [
+ " x y\n",
+ "0 0.000000 0.000000\n",
+ "1 0.256457 0.507309"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df[:2]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0ddef5ef-94a3-4afd-9c70-ddee9694f512",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "New Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.7"
+ },
+ "toc-showtags": true
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected.md b/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected.md
new file mode 100644
index 00000000000..af34d6eb8c3
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected.md
@@ -0,0 +1,5 @@
+%% Cell type:code id:123 tags:
+
+``` python
+print("G'bye")
+```
diff --git a/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected_symbols.txt
new file mode 100644
index 00000000000..cb35f88c897
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/expected_symbols.txt
@@ -0,0 +1,5 @@
+.cells.0
+
+.cells.0.source
+.cells.0.source.0
+
diff --git a/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/input.ipynb b/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/input.ipynb
new file mode 100644
index 00000000000..42f4b39b365
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/testdata/unknown_output_type/input.ipynb
@@ -0,0 +1,27 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "123",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "unknown_output",
+ "text": [
+ "G'bye\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\"G'bye\")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "language": "python"
+ }
+ }
+}
diff --git a/vendor/gems/ipynbdiff/spec/transformer_spec.rb b/vendor/gems/ipynbdiff/spec/transformer_spec.rb
new file mode 100644
index 00000000000..8f9527847fa
--- /dev/null
+++ b/vendor/gems/ipynbdiff/spec/transformer_spec.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+require 'rspec'
+require 'ipynbdiff'
+require 'json'
+require 'rspec-parameterized'
+
+BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'testdata')
+
+def read_file(*paths)
+ File.read(File.join(BASE_PATH, *paths))
+end
+
+def default_config
+ @default_config ||= {
+ include_frontmatter: false,
+ hide_images: false
+ }
+end
+
+def from_ipynb
+ @from_ipynb ||= read_file('from.ipynb')
+end
+
+def read_notebook(input_path)
+ read_file(input_path, 'input.ipynb')
+rescue Errno::ENOENT
+ from_ipynb
+end
+
+describe IpynbDiff::Transformer do
+ describe 'When notebook is valid' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:ctx, :test_case, :config) do
+ 'renders metadata' | 'no_cells' | { include_frontmatter: true }
+ 'is empty for no cells, but metadata is false' | 'no_cells_no_metadata' | {}
+ 'adds markdown cell' | 'only_md' | {}
+ 'adds block with only one line of markdown' | 'single_line_md' | {}
+ 'adds raw block' | 'only_raw' | {}
+ 'code cell, but no output' | 'only_code' | {}
+ 'code cell, but no language' | 'only_code_no_language' | {}
+ 'code cell, but no kernelspec' | 'only_code_no_kernelspec' | {}
+ 'code cell, but no nb metadata' | 'only_code_no_metadata' | {}
+ 'text output' | 'text_output' | {}
+ 'ignores html output' | 'ignore_html_output' | {}
+ 'extracts png output along with text' | 'text_png_output' | {}
+ 'embeds svg as image' | 'svg' | {}
+ 'extracts latex output' | 'latex_output' | {}
+ 'extracts error output' | 'error_output' | {}
+ 'does not fetch tags if there is no cell metadata' | 'no_metadata_on_cell' | {}
+ 'generates :percent decorator' | 'percent_decorator' | {}
+ 'parses stream output' | 'stream_text' | {}
+ 'ignores unknown output type' | 'unknown_output_type' | {}
+ 'handles backslash correctly' | 'backslash_as_last_char' | {}
+ 'multiline png output' | 'multiline_png_output' | {}
+ 'hides images when option passed' | 'hide_images' | { hide_images: true }
+ end
+
+ with_them do
+ let(:expected_md) { read_file(test_case, 'expected.md') }
+ let(:expected_symbols) { read_file(test_case, 'expected_symbols.txt') }
+ let(:input) { read_notebook(test_case) }
+ let(:transformed) { IpynbDiff::Transformer.new(**default_config.merge(config)).transform(input) }
+
+ it 'generates the expected markdown' do
+ expect(transformed.as_text).to eq expected_md
+ end
+
+ it 'generates the expected symbol map' do
+ expect(transformed.blocks.map { |b| b[:source_symbol] }.join("\n")).to eq expected_symbols
+ end
+ end
+ end
+
+ context 'When the notebook is invalid' do
+ [
+ ['because the json is invalid', 'a'],
+ ['because it doesnt have the cell tag', '{"metadata":[]}']
+ ].each do |ctx, notebook|
+ context ctx do
+ it 'raises error' do
+ expect do
+ IpynbDiff::Transformer.new.transform(notebook)
+ end.to raise_error(IpynbDiff::InvalidNotebookError)
+ end
+ end
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index a9378975e33..60129b671c9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3860,10 +3860,10 @@ confusing-browser-globals@^1.0.10:
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59"
integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==
-connect-history-api-fallback@^1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
- integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
+connect-history-api-fallback@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8"
+ integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==
consola@^2.15.0:
version "2.15.3"
@@ -13103,10 +13103,10 @@ webpack-dev-middleware@^5.3.1:
range-parser "^1.2.1"
schema-utils "^4.0.0"
-webpack-dev-server@4.9.2:
- version "4.9.2"
- resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.2.tgz#c188db28c7bff12f87deda2a5595679ebbc3c9bc"
- integrity sha512-H95Ns95dP24ZsEzO6G9iT+PNw4Q7ltll1GfJHV4fKphuHWgKFzGHWi4alTlTnpk1SPPk41X+l2RB7rLfIhnB9Q==
+webpack-dev-server@4.9.3:
+ version "4.9.3"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz#2360a5d6d532acb5410a668417ad549ee3b8a3c9"
+ integrity sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==
dependencies:
"@types/bonjour" "^3.5.9"
"@types/connect-history-api-fallback" "^1.3.5"
@@ -13120,7 +13120,7 @@ webpack-dev-server@4.9.2:
chokidar "^3.5.3"
colorette "^2.0.10"
compression "^1.7.4"
- connect-history-api-fallback "^1.6.0"
+ connect-history-api-fallback "^2.0.0"
default-gateway "^6.0.3"
express "^4.17.3"
graceful-fs "^4.2.6"