diff options
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 @@ -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" |