diff options
109 files changed, 2869 insertions, 206 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 bd08985692f..a4d63782aac 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/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue index 892c631f8ea..7dd9dc4a01f 100644 --- a/app/assets/javascripts/issues/show/components/description.vue +++ b/app/assets/javascripts/issues/show/components/description.vue @@ -1,11 +1,5 @@ <script> -import { - GlSafeHtmlDirective as SafeHtml, - GlModal, - GlToast, - GlTooltip, - GlModalDirective, -} from '@gitlab/ui'; +import { GlSafeHtmlDirective as SafeHtml, GlToast, GlTooltip, GlModalDirective } from '@gitlab/ui'; import $ from 'jquery'; import Sortable from 'sortablejs'; import Vue from 'vue'; @@ -20,11 +14,16 @@ import { getSortableDefaultOptions, isDragging } from '~/sortable/utils'; import TaskList from '~/task_list'; import Tracking from '~/tracking'; import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; +import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql'; +import createWorkItemFromTaskMutation from '~/work_items/graphql/create_work_item_from_task.mutation.graphql'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; -import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants'; -import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; +import { + TRACKING_CATEGORY_SHOW, + TASK_TYPE_NAME, + WIDGET_TYPE_DESCRIPTION, +} from '~/work_items/constants'; import animateMixin from '../mixins/animate'; import { convertDescriptionWithNewSort } from '../utils'; @@ -40,12 +39,11 @@ export default { GlModal: GlModalDirective, }, components: { - GlModal, - CreateWorkItem, GlTooltip, WorkItemDetailModal, }, mixins: [animateMixin, glFeatureFlagMixin(), Tracking.mixin()], + inject: ['fullPath'], props: { canUpdate: { type: Boolean, @@ -103,6 +101,7 @@ export default { workItemId: isPositiveInteger(workItemId) ? convertToGraphQLId(TYPE_WORK_ITEM, workItemId) : undefined, + workItemTypes: [], }; }, apollo: { @@ -117,11 +116,28 @@ export default { return !this.workItemId || !this.workItemsEnabled; }, }, + workItemTypes: { + query: projectWorkItemTypesQuery, + variables() { + return { + fullPath: this.fullPath, + }; + }, + update(data) { + return data.workspace?.workItemTypes?.nodes; + }, + skip() { + return !this.workItemsEnabled; + }, + }, }, computed: { workItemsEnabled() { return this.glFeatures.workItems; }, + taskWorkItemType() { + return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id; + }, issueGid() { return this.issueId ? convertToGraphQLId(TYPE_WORK_ITEM, this.issueId) : null; }, @@ -344,8 +360,8 @@ export default { <use href="${gon.sprite_icons}#doc-new"></use> </svg> `; - button.setAttribute('aria-label', s__('WorkItem|Convert to work item')); - button.addEventListener('click', () => this.openCreateTaskModal(button)); + button.setAttribute('aria-label', s__('WorkItem|Create task')); + button.addEventListener('click', () => this.handleCreateTask(button)); this.insertButtonNextToTaskText(item, button); }); }, @@ -386,17 +402,11 @@ export default { lineNumberEnd: lineNumbers[1], }; }, - openCreateTaskModal(el) { - this.setActiveTask(el); - this.$refs.modal.show(); - }, - closeCreateTaskModal() { - this.$refs.modal.hide(); - }, openWorkItemDetailModal(el) { if (!el) { return; } + this.setActiveTask(el); this.$refs.detailsModal.show(); }, @@ -404,9 +414,54 @@ export default { this.workItemId = undefined; this.updateWorkItemIdUrlQuery(undefined); }, - handleCreateTask(description) { - this.$emit('updateDescription', description); - this.closeCreateTaskModal(); + async handleCreateTask(el) { + this.setActiveTask(el); + try { + const { data } = await this.$apollo.mutate({ + mutation: createWorkItemFromTaskMutation, + variables: { + input: { + id: this.issueGid, + workItemData: { + lockVersion: this.lockVersion, + title: this.activeTask.title, + lineNumberStart: Number(this.activeTask.lineNumberStart), + lineNumberEnd: Number(this.activeTask.lineNumberEnd), + workItemTypeId: this.taskWorkItemType, + }, + }, + }, + update(store, { data: { workItemCreateFromTask } }) { + const { newWorkItem } = workItemCreateFromTask; + + store.writeQuery({ + query: workItemQuery, + variables: { + id: newWorkItem.id, + }, + data: { + workItem: newWorkItem, + }, + }); + }, + }); + + const { workItem, newWorkItem } = data.workItemCreateFromTask; + + const updatedDescription = workItem?.widgets?.find( + (widget) => widget.type === WIDGET_TYPE_DESCRIPTION, + )?.descriptionHtml; + + this.$emit('updateDescription', updatedDescription); + this.workItemId = newWorkItem.id; + this.openWorkItemDetailModal(el); + } catch (error) { + createFlash({ + message: s__('WorkItem|Something went wrong when creating a work item. Please try again'), + error, + captureError: true, + }); + } }, handleDeleteTask(description) { this.$emit('updateDescription', description); @@ -452,19 +507,6 @@ export default { data-testid="textarea" > </textarea> - - <gl-modal ref="modal" size="lg" modal-id="create-task-modal" hide-footer body-class="gl-p-0!"> - <create-work-item - is-modal - :initial-title="activeTask.title" - :issue-gid="issueGid" - :lock-version="lockVersion" - :line-number-start="activeTask.lineNumberStart" - :line-number-end="activeTask.lineNumberEnd" - @closeModal="closeCreateTaskModal" - @onCreate="handleCreateTask" - /> - </gl-modal> <work-item-detail-modal ref="detailsModal" :can-update="canUpdate" @@ -478,7 +520,7 @@ export default { /> <template v-if="workItemsEnabled"> <gl-tooltip v-for="item in taskButtons" :key="item" :target="item"> - {{ s__('WorkItem|Convert to work item') }} + {{ s__('WorkItem|Create task') }} </gl-tooltip> </template> </div> diff --git a/app/assets/javascripts/work_items/components/item_state.vue b/app/assets/javascripts/work_items/components/item_state.vue index 69670d3471c..2dc8e3a1101 100644 --- a/app/assets/javascripts/work_items/components/item_state.vue +++ b/app/assets/javascripts/work_items/components/item_state.vue @@ -54,7 +54,7 @@ export default { :label-for="$options.labelId" label-cols="3" label-cols-lg="2" - label-class="gl-pb-0!" + label-class="gl-pb-0! gl-overflow-wrap-break" class="gl-align-items-center" > <gl-form-select diff --git a/app/assets/javascripts/work_items/components/item_title.vue b/app/assets/javascripts/work_items/components/item_title.vue index ce2fa158596..19fbad4eaa3 100644 --- a/app/assets/javascripts/work_items/components/item_title.vue +++ b/app/assets/javascripts/work_items/components/item_title.vue @@ -40,7 +40,7 @@ export default { <template> <h2 - class="gl-font-weight-normal gl-sm-font-weight-bold gl-my-5 gl-w-full" + class="gl-font-weight-normal gl-sm-font-weight-bold gl-mb-5 gl-mt-0 gl-w-full" :class="{ 'gl-cursor-not-allowed': disabled }" aria-labelledby="item-title" > diff --git a/app/assets/javascripts/work_items/components/work_item_assignees.vue b/app/assets/javascripts/work_items/components/work_item_assignees.vue index 46920969415..45add8c8d74 100644 --- a/app/assets/javascripts/work_items/components/work_item_assignees.vue +++ b/app/assets/javascripts/work_items/components/work_item_assignees.vue @@ -137,17 +137,19 @@ export default { </script> <template> - <div class="gl-display-flex gl-mb-4 work-item-assignees gl-relative"> - <span class="gl-font-weight-bold gl-w-15 gl-pt-2" data-testid="assignees-title">{{ - assigneeText - }}</span> + <div class="form-row gl-mb-5 work-item-assignees gl-relative"> + <span + class="gl-font-weight-bold col-lg-2 col-3 gl-pt-2 min-w-fit-content gl-overflow-wrap-break" + data-testid="assignees-title" + >{{ assigneeText }}</span + > <gl-token-selector ref="tokenSelector" v-model="localAssignees" :container-class="containerClass" + class="gl-flex-grow-1 gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base col-9 gl-align-self-start" :dropdown-items="searchUsers" :loading="isLoading" - class="gl-w-full gl-border gl-border-white gl-hover-border-gray-200 gl-rounded-base" @input="focusTokenSelector" @text-input="debouncedSearchKeyUpdate" @focus="handleFocus" diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue index 5a85fcdd7ac..90e3cd45cb4 100644 --- a/app/assets/javascripts/work_items/components/work_item_description.vue +++ b/app/assets/javascripts/work_items/components/work_item_description.vue @@ -35,7 +35,7 @@ export default { isEditing: false, isSubmitting: false, isSubmittingWithKeydown: false, - desc: '', + descriptionText: '', }; }, apollo: { @@ -71,16 +71,17 @@ export default { descriptionHtml() { return this.workItemDescription?.descriptionHtml; }, - descriptionText: { - get() { - return this.desc; - }, - set(desc) { - this.desc = desc; - }, + descriptionEmpty() { + return this.descriptionHtml?.trim() === ''; }, workItemDescription() { - return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_DESCRIPTION); + const descriptionWidget = this.workItem?.widgets?.find( + (widget) => widget.type === WIDGET_TYPE_DESCRIPTION, + ); + return { + ...descriptionWidget, + description: descriptionWidget?.description || '', + }; }, workItemType() { return this.workItem?.workItemType?.name; @@ -95,14 +96,14 @@ export default { async startEditing() { this.isEditing = true; - this.desc = getDraft(this.autosaveKey) || this.workItemDescription?.description || ''; + this.descriptionText = getDraft(this.autosaveKey) || this.workItemDescription?.description; await this.$nextTick(); this.$refs.textarea.focus(); }, async cancelEditing() { - const isDirty = this.desc !== this.workItemDescription?.description; + const isDirty = this.descriptionText !== this.workItemDescription?.description; if (isDirty) { const msg = s__('WorkItem|Are you sure you want to cancel editing?'); @@ -125,7 +126,7 @@ export default { return; } - updateDraft(this.autosaveKey, this.desc); + updateDraft(this.autosaveKey, this.descriptionText); }, async updateWorkItem(event) { if (event.key) { @@ -171,25 +172,10 @@ export default { <template> <gl-form-group v-if="isEditing" - class="gl-pt-5 gl-mb-5 gl-mt-0! gl-border-t! gl-border-b" + class="gl-my-5" :label="__('Description')" label-for="work-item-description" - label-class="gl-float-left" > - <div class="gl-display-flex gl-justify-content-flex-end"> - <gl-button class="gl-ml-auto" data-testid="cancel" @click="cancelEditing">{{ - __('Cancel') - }}</gl-button> - <gl-button - class="js-no-auto-disable gl-ml-4" - category="primary" - variant="confirm" - :loading="isSubmitting" - data-testid="save-description" - @click="updateWorkItem" - >{{ __('Save') }}</gl-button - > - </div> <markdown-field can-attach-file :textarea-value="descriptionText" @@ -216,19 +202,35 @@ export default { ></textarea> </template> </markdown-field> - </gl-form-group> - <div v-else class="gl-pt-5 gl-mb-5 gl-border-t gl-border-b"> + <div class="gl-display-flex"> - <h3 class="gl-font-base gl-mt-0">{{ __('Description') }}</h3> + <gl-button + category="primary" + variant="confirm" + :loading="isSubmitting" + data-testid="save-description" + @click="updateWorkItem" + >{{ __('Save') }}</gl-button + > + <gl-button category="tertiary" class="gl-ml-3" data-testid="cancel" @click="cancelEditing">{{ + __('Cancel') + }}</gl-button> + </div> + </gl-form-group> + <div v-else class="gl-mb-5"> + <div class="gl-display-flex gl-align-items-center gl-mb-5"> + <h3 class="gl-font-base gl-my-0">{{ __('Description') }}</h3> <gl-button v-if="canEdit" class="gl-ml-auto" icon="pencil" data-testid="edit-description" + :aria-label="__('Edit')" @click="startEditing" - >{{ __('Edit') }}</gl-button - > + /> </div> - <div v-safe-html="descriptionHtml" class="md gl-mb-5"></div> + + <div v-if="descriptionEmpty" class="gl-text-secondary gl-mb-5">{{ __('None') }}</div> + <div v-else v-safe-html="descriptionHtml" class="md gl-mb-5 gl-min-h-8"></div> </div> </template> diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index 5272df2d53f..606d65a08f8 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -101,7 +101,7 @@ export default { </script> <template> - <section> + <section class="gl-pt-5"> <gl-alert v-if="error" variant="danger" @dismiss="error = undefined"> {{ error }} </gl-alert> @@ -113,6 +113,7 @@ export default { </gl-skeleton-loader> </div> <template v-else> + <div class="gl-font-weight-bold gl-text-secondary gl-mb-2">{{ workItemType }}</div> <div class="gl-display-flex gl-align-items-start"> <work-item-title :work-item-id="workItem.id" @@ -125,11 +126,16 @@ export default { <work-item-actions :work-item-id="workItem.id" :can-delete="canDelete" - class="gl-ml-auto gl-mt-6" + class="gl-mt-4" @deleteWorkItem="$emit('deleteWorkItem')" @error="error = $event" /> </div> + <work-item-state + :work-item="workItem" + :work-item-parent-id="workItemParentId" + @error="error = $event" + /> <template v-if="workItemsMvc2Enabled"> <work-item-assignees v-if="workItemAssignees" @@ -138,14 +144,10 @@ export default { /> <work-item-weight v-if="workItemWeight" :weight="workItemWeight.weight" /> </template> - <work-item-state - :work-item="workItem" - :work-item-parent-id="workItemParentId" - @error="error = $event" - /> <work-item-description v-if="hasDescriptionWidget" :work-item-id="workItem.id" + class="gl-pt-5" @error="error = $event" /> </template> diff --git a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue index d1c8022ac57..50753c79741 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue @@ -104,7 +104,6 @@ export default { size="lg" modal-id="work-item-detail-modal" header-class="gl-p-0 gl-pb-2!" - body-class="gl-pb-6!" @hide="closeModal" > <gl-alert v-if="error" variant="danger" @dismiss="error = false"> diff --git a/app/assets/javascripts/work_items/components/work_item_weight.vue b/app/assets/javascripts/work_items/components/work_item_weight.vue index b0f2b3aa14a..72e678151e9 100644 --- a/app/assets/javascripts/work_items/components/work_item_weight.vue +++ b/app/assets/javascripts/work_items/components/work_item_weight.vue @@ -19,8 +19,10 @@ export default { </script> <template> - <div v-if="hasIssueWeightsFeature" class="gl-mb-5"> - <span class="gl-display-inline-block gl-font-weight-bold gl-w-15">{{ __('Weight') }}</span> - {{ weightText }} + <div v-if="hasIssueWeightsFeature" class="gl-mb-5 form-row"> + <span class="gl-font-weight-bold col-lg-2 col-3 gl-overflow-wrap-break">{{ + __('Weight') + }}</span> + <span class="gl-ml-5">{{ weightText }}</span> </div> </template> diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index 2df4978a319..d5ec1900a86 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -13,7 +13,7 @@ export const i18n = { updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'), }; -export const DEFAULT_MODAL_TYPE = 'Task'; +export const TASK_TYPE_NAME = 'Task'; export const WIDGET_TYPE_ASSIGNEE = 'ASSIGNEES'; export const WIDGET_TYPE_DESCRIPTION = 'DESCRIPTION'; diff --git a/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql b/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql index b25210f5c74..ccfe62cc585 100644 --- a/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql +++ b/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql @@ -1,8 +1,12 @@ +#import "./work_item.fragment.graphql" + mutation workItemCreateFromTask($input: WorkItemCreateFromTaskInput!) { workItemCreateFromTask(input: $input) { workItem { - id - descriptionHtml + ...WorkItem + } + newWorkItem { + ...WorkItem } errors } diff --git a/app/assets/javascripts/work_items/pages/create_work_item.vue b/app/assets/javascripts/work_items/pages/create_work_item.vue index 04c6a61689c..0047b03108a 100644 --- a/app/assets/javascripts/work_items/pages/create_work_item.vue +++ b/app/assets/javascripts/work_items/pages/create_work_item.vue @@ -6,7 +6,6 @@ import workItemQuery from '../graphql/work_item.query.graphql'; import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql'; import createWorkItemFromTaskMutation from '../graphql/create_work_item_from_task.mutation.graphql'; import projectWorkItemTypesQuery from '../graphql/project_work_item_types.query.graphql'; -import { DEFAULT_MODAL_TYPE } from '../constants'; import ItemTitle from '../components/item_title.vue'; @@ -24,11 +23,6 @@ export default { }, inject: ['fullPath'], props: { - isModal: { - type: Boolean, - required: false, - default: false, - }, initialTitle: { type: String, required: false, @@ -78,13 +72,6 @@ export default { text: node.name, })); }, - result() { - if (!this.selectedWorkItemType && this.isModal) { - this.selectedWorkItemType = this.formOptions.find( - (options) => options.text === DEFAULT_MODAL_TYPE, - )?.value; - } - }, error() { this.error = this.$options.fetchTypesErrorText; }, @@ -104,11 +91,7 @@ export default { methods: { async createWorkItem() { this.loading = true; - if (this.isModal) { - await this.createWorkItemFromTask(); - } else { - await this.createStandaloneWorkItem(); - } + await this.createStandaloneWorkItem(); this.loading = false; }, async createStandaloneWorkItem() { @@ -174,11 +157,7 @@ export default { this.title = title; }, handleCancelClick() { - if (!this.isModal) { - this.$router.go(-1); - return; - } - this.$emit('closeModal'); + this.$router.go(-1); }, }, }; @@ -187,7 +166,7 @@ export default { <template> <form @submit.prevent="createWorkItem"> <gl-alert v-if="error" variant="danger" @dismiss="error = null">{{ error }}</gl-alert> - <div :class="{ 'gl-px-5': isModal }" data-testid="content"> + <div data-testid="content"> <item-title :title="initialTitle" data-testid="title-input" @title-input="handleTitleInput" /> <div> <gl-loading-icon @@ -203,14 +182,11 @@ export default { /> </div> </div> - <div - class="gl-bg-gray-10 gl-py-5 gl-px-6 gl-mt-4" - :class="{ 'gl-display-flex gl-justify-content-end': isModal }" - > + <div class="gl-bg-gray-10 gl-py-5 gl-px-6 gl-mt-4"> <gl-button variant="confirm" :disabled="isButtonDisabled" - :class="{ 'gl-mr-3': !isModal }" + class="gl-mr-3" :loading="loading" data-testid="create-button" type="submit" @@ -221,7 +197,6 @@ export default { type="button" data-testid="cancel-button" class="gl-order-n1" - :class="{ 'gl-mr-3': isModal }" @click="handleCancelClick" > {{ __('Cancel') }} diff --git a/app/models/users/group_callout.rb b/app/models/users/group_callout.rb index 373bc30889f..0ea7b8199aa 100644 --- a/app/models/users/group_callout.rb +++ b/app/models/users/group_callout.rb @@ -16,7 +16,8 @@ module Users storage_enforcement_banner_third_enforcement_threshold: 5, storage_enforcement_banner_fourth_enforcement_threshold: 6, preview_user_over_limit_free_plan_alert: 7, # EE-only - user_reached_limit_free_plan_alert: 8 # EE-only + user_reached_limit_free_plan_alert: 8, # EE-only + free_group_limited_alert: 9 # EE-only } validates :group, presence: true diff --git a/config/feature_flags/development/blame_page_pagination.yml b/config/feature_flags/development/blame_page_pagination.yml index 49fb1a7f605..8465b40b4c5 100644 --- a/config/feature_flags/development/blame_page_pagination.yml +++ b/config/feature_flags/development/blame_page_pagination.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/360927 milestone: '15.0' type: development group: group::source code -default_enabled: false +default_enabled: true diff --git a/doc/administration/audit_event_streaming.md b/doc/administration/audit_event_streaming.md index bd67d62f384..5fc92c5fb6c 100644 --- a/doc/administration/audit_event_streaming.md +++ b/doc/administration/audit_event_streaming.md @@ -146,10 +146,10 @@ Destination is deleted if: ## Custom HTTP header values > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/361216) in GitLab 15.1 [with a flag](feature_flags.md) named `streaming_audit_event_headers`. Disabled by default. -> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/362941) in GitLab 15.2. +> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/362941) in GitLab 15.2. FLAG: -On self-managed GitLab, by default this feature is not available. To make it available per group, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `streaming_audit_event_headers`. +On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../administration/feature_flags.md) named `streaming_audit_event_headers`. On GitLab.com, this feature is available. Each streaming destination can have up to 20 custom HTTP headers included with each streamed event. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 9de564449ef..336aecda991 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -11520,7 +11520,7 @@ Represents an external resource to send audit events to. | ---- | ---- | ----------- | | <a id="externalauditeventdestinationdestinationurl"></a>`destinationUrl` | [`String!`](#string) | External destination to send audit events to. | | <a id="externalauditeventdestinationgroup"></a>`group` | [`Group!`](#group) | Group the destination belongs to. | -| <a id="externalauditeventdestinationheaders"></a>`headers` | [`AuditEventStreamingHeaderConnection!`](#auditeventstreamingheaderconnection) | List of additional HTTP headers sent with each event. Available only when feature flag `streaming_audit_event_headers` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) | +| <a id="externalauditeventdestinationheaders"></a>`headers` | [`AuditEventStreamingHeaderConnection!`](#auditeventstreamingheaderconnection) | List of additional HTTP headers sent with each event. Available only when feature flag `streaming_audit_event_headers` is enabled. This flag is enabled by default. (see [Connections](#connections)) | | <a id="externalauditeventdestinationid"></a>`id` | [`ID!`](#id) | ID of the destination. | | <a id="externalauditeventdestinationverificationtoken"></a>`verificationToken` | [`String!`](#string) | Verification token to validate source of event. | diff --git a/doc/user/tasks.md b/doc/user/tasks.md index fc49661c61c..5112e1afaa8 100644 --- a/doc/user/tasks.md +++ b/doc/user/tasks.md @@ -32,8 +32,7 @@ to work items and adding custom work item types, visit To create a task: 1. In an issue description, create a [task list](markdown.md#task-lists). -1. Hover over a task item and select **Convert to work item** (**{doc-new}**). -1. Confirm or edit the title, and select **Create work item**. +1. Hover over a task item and select **Create task** (**{doc-new}**). ## Edit a task diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ecea30cb802..207ec7fffe5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6214,6 +6214,12 @@ msgstr "" msgid "Billing|You can begin moving members in %{namespaceName} now. A member loses access to the group when you turn off %{strongStart}In a seat%{strongEnd}. If over 5 members have %{strongStart}In a seat%{strongEnd} enabled after June 22, 2022, we'll select the 5 members who maintain access. We'll first count members that have Owner and Maintainer roles, then the most recently active members until we reach 5 members. The remaining members will get a status of Over limit and lose access to the group." msgstr "" +msgid "Billing|Your free group is now limited to %{free_user_limit} members" +msgstr "" + +msgid "Billing|Your group recently changed to use the Free plan. Free groups are limited to %{free_user_limit} members and the remaining members will get a status of over-limit and lose access to the group. You can free up space for new members by removing those who no longer need access or toggling them to over-limit. To get an unlimited number of members, you can %{link_start}upgrade%{link_end} to a paid tier." +msgstr "" + msgid "Bitbucket Server Import" msgstr "" @@ -43524,7 +43530,7 @@ msgstr "" msgid "WorkItem|Collapse child items" msgstr "" -msgid "WorkItem|Convert to work item" +msgid "WorkItem|Create task" msgstr "" msgid "WorkItem|Create work item" diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js index 2cc27309e59..b8a2da4fa47 100644 --- a/spec/frontend/issues/show/components/description_spec.js +++ b/spec/frontend/issues/show/components/description_spec.js @@ -15,10 +15,15 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import Description from '~/issues/show/components/description.vue'; import { updateHistory } from '~/lib/utils/url_utility'; import workItemQuery from '~/work_items/graphql/work_item.query.graphql'; +import workItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql'; +import createWorkItemFromTaskMutation from '~/work_items/graphql/create_work_item_from_task.mutation.graphql'; import TaskList from '~/task_list'; import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants'; -import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; +import { + projectWorkItemTypesQueryResponse, + createWorkItemFromTaskMutationResponse, +} from 'jest/work_items/mock_data'; import { descriptionProps as initialProps, descriptionHtmlWithCheckboxes, @@ -46,6 +51,10 @@ const workItemQueryResponse = { }; const queryHandler = jest.fn().mockResolvedValue(workItemQueryResponse); +const workItemTypesQueryHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse); +const createWorkItemFromTaskSuccessHandler = jest + .fn() + .mockResolvedValue(createWorkItemFromTaskMutationResponse); describe('Description component', () => { let wrapper; @@ -60,18 +69,24 @@ describe('Description component', () => { const findTooltips = () => wrapper.findAllComponents(GlTooltip); const findModal = () => wrapper.findComponent(GlModal); - const findCreateWorkItem = () => wrapper.findComponent(CreateWorkItem); const findWorkItemDetailModal = () => wrapper.findComponent(WorkItemDetailModal); - function createComponent({ props = {}, provide = {} } = {}) { + function createComponent({ props = {}, provide } = {}) { wrapper = shallowMountExtended(Description, { propsData: { issueId: 1, ...initialProps, ...props, }, - provide, - apolloProvider: createMockApollo([[workItemQuery, queryHandler]]), + provide: { + fullPath: 'gitlab-org/gitlab-test', + ...provide, + }, + apolloProvider: createMockApollo([ + [workItemQuery, queryHandler], + [workItemTypesQuery, workItemTypesQueryHandler], + [createWorkItemFromTaskMutation, createWorkItemFromTaskSuccessHandler], + ]), mocks: { $toast, }, @@ -299,24 +314,16 @@ describe('Description component', () => { }); it('does not show a modal by default', () => { - expect(findModal().props('visible')).toBe(false); + expect(findModal().exists()).toBe(false); }); - it('opens a modal when a button is clicked and displays correct title', async () => { - await findConvertToTaskButton().trigger('click'); - expect(findCreateWorkItem().props('initialTitle').trim()).toBe('todo 1'); - }); + it('emits `updateDescription` after creating new work item', async () => { + const newDescription = `<p>New description</p>`; - it('closes the modal on `closeCreateTaskModal` event', async () => { await findConvertToTaskButton().trigger('click'); - findCreateWorkItem().vm.$emit('closeModal'); - expect(hideModal).toHaveBeenCalled(); - }); - it('emits `updateDescription` on `onCreate` event', () => { - const newDescription = `<p>New description</p>`; - findCreateWorkItem().vm.$emit('onCreate', newDescription); - expect(hideModal).toHaveBeenCalled(); + await waitForPromises(); + expect(wrapper.emitted('updateDescription')).toEqual([[newDescription]]); }); diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index 116bf48901d..7fe405a9aac 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -140,13 +140,45 @@ export const createWorkItemFromTaskMutationResponse = { __typename: 'WorkItemCreateFromTaskPayload', errors: [], workItem: { - descriptionHtml: '<p>New description</p>', - id: 'gid://gitlab/WorkItem/13', __typename: 'WorkItem', + description: 'New description', + id: 'gid://gitlab/WorkItem/1', + title: 'Updated title', + state: 'OPEN', + workItemType: { + __typename: 'WorkItemType', + id: 'gid://gitlab/WorkItems::Type/5', + name: 'Task', + }, userPermissions: { deleteWorkItem: false, updateWorkItem: false, }, + widgets: [ + { + __typename: 'WorkItemWidgetDescription', + type: 'DESCRIPTION', + description: 'New description', + descriptionHtml: '<p>New description</p>', + }, + ], + }, + newWorkItem: { + __typename: 'WorkItem', + id: 'gid://gitlab/WorkItem/1000000', + title: 'Updated title', + state: 'OPEN', + description: '', + workItemType: { + __typename: 'WorkItemType', + id: 'gid://gitlab/WorkItems::Type/5', + name: 'Task', + }, + userPermissions: { + deleteWorkItem: false, + updateWorkItem: false, + }, + widgets: [], }, }, }, diff --git a/spec/frontend/work_items/pages/create_work_item_spec.js b/spec/frontend/work_items/pages/create_work_item_spec.js index e89477ed599..fed8be3783a 100644 --- a/spec/frontend/work_items/pages/create_work_item_spec.js +++ b/spec/frontend/work_items/pages/create_work_item_spec.js @@ -9,11 +9,7 @@ import ItemTitle from '~/work_items/components/item_title.vue'; import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql'; import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql'; import createWorkItemFromTaskMutation from '~/work_items/graphql/create_work_item_from_task.mutation.graphql'; -import { - projectWorkItemTypesQueryResponse, - createWorkItemMutationResponse, - createWorkItemFromTaskMutationResponse, -} from '../mock_data'; +import { projectWorkItemTypesQueryResponse, createWorkItemMutationResponse } from '../mock_data'; jest.mock('~/lib/utils/uuids', () => ({ uuids: () => ['testuuid'] })); @@ -25,9 +21,6 @@ describe('Create work item component', () => { const querySuccessHandler = jest.fn().mockResolvedValue(projectWorkItemTypesQueryResponse); const createWorkItemSuccessHandler = jest.fn().mockResolvedValue(createWorkItemMutationResponse); - const createWorkItemFromTaskSuccessHandler = jest - .fn() - .mockResolvedValue(createWorkItemFromTaskMutationResponse); const errorHandler = jest.fn().mockRejectedValue('Houston, we have a problem'); const findAlert = () => wrapper.findComponent(GlAlert); @@ -122,49 +115,6 @@ describe('Create work item component', () => { }); }); - describe('when displayed in a modal', () => { - beforeEach(() => { - createComponent({ - props: { - isModal: true, - }, - mutationHandler: createWorkItemFromTaskSuccessHandler, - }); - }); - - it('emits `closeModal` event on Cancel button click', () => { - findCancelButton().vm.$emit('click'); - - expect(wrapper.emitted('closeModal')).toEqual([[]]); - }); - - it('emits `onCreate` on successful mutation', async () => { - findTitleInput().vm.$emit('title-input', 'Test title'); - - wrapper.find('form').trigger('submit'); - await waitForPromises(); - - expect(wrapper.emitted('onCreate')).toEqual([['<p>New description</p>']]); - }); - - it('does not right margin for create button', () => { - expect(findCreateButton().classes()).not.toContain('gl-mr-3'); - }); - - it('adds right margin for cancel button', () => { - expect(findCancelButton().classes()).toContain('gl-mr-3'); - }); - - it('adds padding for content', () => { - expect(findContent().classes('gl-px-5')).toBe(true); - }); - - it('defaults type to `Task`', async () => { - await waitForPromises(); - expect(findSelect().attributes('value')).toBe('gid://gitlab/WorkItems::Type/3'); - }); - }); - it('displays a loading icon inside dropdown when work items query is loading', () => { createComponent(); 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..9ace051b496 --- /dev/null +++ b/vendor/gems/ipynbdiff/ipynbdiff.gemspec @@ -0,0 +1,34 @@ +# 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.chdir(File.expand_path(__dir__)) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|example)/}) } + end + 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 |