diff options
-rw-r--r-- | app/assets/javascripts/notebook/cells/code.vue | 15 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/code/index.vue | 3 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/output/html.vue | 15 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/output/image.vue | 20 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/output/index.vue | 104 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/cells/prompt.vue | 10 | ||||
-rw-r--r-- | app/assets/javascripts/notebook/index.vue | 4 | ||||
-rw-r--r-- | changelogs/unreleased/notebook-multiple-outputs.yml | 5 | ||||
-rw-r--r-- | spec/javascripts/notebook/cells/output/html_spec.js | 2 | ||||
-rw-r--r-- | spec/javascripts/notebook/cells/output/index_spec.js | 27 |
10 files changed, 118 insertions, 87 deletions
diff --git a/app/assets/javascripts/notebook/cells/code.vue b/app/assets/javascripts/notebook/cells/code.vue index bd6736152f5..eefc801ed7a 100644 --- a/app/assets/javascripts/notebook/cells/code.vue +++ b/app/assets/javascripts/notebook/cells/code.vue @@ -1,11 +1,12 @@ <script> -import CodeCell from './code/index.vue'; +import CodeOutput from './code/index.vue'; import OutputCell from './output/index.vue'; export default { + name: 'CodeCell', components: { - 'code-cell': CodeCell, - 'output-cell': OutputCell, + CodeOutput, + OutputCell, }, props: { cell: { @@ -29,8 +30,8 @@ export default { hasOutput() { return this.cell.outputs.length; }, - output() { - return this.cell.outputs[0]; + outputs() { + return this.cell.outputs; }, }, }; @@ -38,7 +39,7 @@ export default { <template> <div class="cell"> - <code-cell + <code-output :raw-code="rawInputCode" :count="cell.execution_count" :code-css-class="codeCssClass" @@ -47,7 +48,7 @@ export default { <output-cell v-if="hasOutput" :count="cell.execution_count" - :output="output" + :outputs="outputs" :code-css-class="codeCssClass" /> </div> diff --git a/app/assets/javascripts/notebook/cells/code/index.vue b/app/assets/javascripts/notebook/cells/code/index.vue index 8bf2431c4c6..98b6cdd0944 100644 --- a/app/assets/javascripts/notebook/cells/code/index.vue +++ b/app/assets/javascripts/notebook/cells/code/index.vue @@ -3,8 +3,9 @@ import Prism from '../../lib/highlight'; import Prompt from '../prompt.vue'; export default { + name: 'CodeOutput', components: { - prompt: Prompt, + Prompt, }, props: { count: { diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue index c6fc786fa76..8dc2d73af9b 100644 --- a/app/assets/javascripts/notebook/cells/output/html.vue +++ b/app/assets/javascripts/notebook/cells/output/html.vue @@ -4,13 +4,21 @@ import Prompt from '../prompt.vue'; export default { components: { - prompt: Prompt, + Prompt, }, props: { + count: { + type: Number, + required: true, + }, rawCode: { type: String, required: true, }, + index: { + type: Number, + required: true, + }, }, computed: { sanitizedOutput() { @@ -21,13 +29,16 @@ export default { }, }); }, + showOutput() { + return this.index === 0; + }, }, }; </script> <template> <div class="output"> - <prompt /> + <prompt type="Out" :count="count" :show-output="showOutput" /> <div v-html="sanitizedOutput"></div> </div> </template> diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue index fe8c81398fb..f1130275525 100644 --- a/app/assets/javascripts/notebook/cells/output/image.vue +++ b/app/assets/javascripts/notebook/cells/output/image.vue @@ -6,6 +6,10 @@ export default { prompt: Prompt, }, props: { + count: { + type: Number, + required: true, + }, outputType: { type: String, required: true, @@ -14,10 +18,24 @@ export default { type: String, required: true, }, + index: { + type: Number, + required: true, + }, + }, + computed: { + imgSrc() { + return `data:${this.outputType};base64,${this.rawCode}`; + }, + showOutput() { + return this.index === 0; + }, }, }; </script> <template> - <div class="output"><prompt /> <img :src="'data:' + outputType + ';base64,' + rawCode" /></div> + <div class="output"> + <prompt type="out" :count="count" :show-output="showOutput" /> <img :src="imgSrc" /> + </div> </template> diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue index bd0bcc0d819..c5ae7e7ee10 100644 --- a/app/assets/javascripts/notebook/cells/output/index.vue +++ b/app/assets/javascripts/notebook/cells/output/index.vue @@ -1,14 +1,9 @@ <script> -import CodeCell from '../code/index.vue'; -import Html from './html.vue'; -import Image from './image.vue'; +import CodeOutput from '../code/index.vue'; +import HtmlOutput from './html.vue'; +import ImageOutput from './image.vue'; export default { - components: { - 'code-cell': CodeCell, - 'html-output': Html, - 'image-output': Image, - }, props: { codeCssClass: { type: String, @@ -20,68 +15,69 @@ export default { required: false, default: 0, }, - output: { - type: Object, + outputs: { + type: Array, required: true, - default: () => ({}), }, }, - computed: { - componentName() { - if (this.output.text) { - return 'code-cell'; - } else if (this.output.data['image/png']) { - return 'image-output'; - } else if (this.output.data['text/html']) { - return 'html-output'; - } else if (this.output.data['image/svg+xml']) { - return 'html-output'; - } + data() { + return { + outputType: '', + }; + }, + methods: { + dataForType(output, type) { + let data = output.data[type]; - return 'code-cell'; - }, - rawCode() { - if (this.output.text) { - return this.output.text.join(''); + if (typeof data === 'object') { + data = data.join(''); } - return this.dataForType(this.outputType); + return data; }, - outputType() { - if (this.output.text) { - return ''; - } else if (this.output.data['image/png']) { - return 'image/png'; - } else if (this.output.data['text/html']) { - return 'text/html'; - } else if (this.output.data['image/svg+xml']) { - return 'image/svg+xml'; + getComponent(output) { + if (output.text) { + return CodeOutput; + } else if (output.data['image/png']) { + this.outputType = 'image/png'; + + return ImageOutput; + } else if (output.data['text/html']) { + this.outputType = 'text/html'; + + return HtmlOutput; + } else if (output.data['image/svg+xml']) { + this.outputType = 'image/svg+xml'; + + return HtmlOutput; } - return 'text/plain'; + this.outputType = 'text/plain'; + return CodeOutput; }, - }, - methods: { - dataForType(type) { - let data = this.output.data[type]; - - if (typeof data === 'object') { - data = data.join(''); + rawCode(output) { + if (output.text) { + return output.text.join(''); } - return data; + return this.dataForType(output, this.outputType); }, }, }; </script> <template> - <component - :is="componentName" - :output-type="outputType" - :count="count" - :raw-code="rawCode" - :code-css-class="codeCssClass" - type="output" - /> + <div> + <component + :is="getComponent(output)" + v-for="(output, index) in outputs" + :key="index" + type="output" + :output-type="outputType" + :count="count" + :index="index" + :raw-code="rawCode(output)" + :code-css-class="codeCssClass" + /> + </div> </template> diff --git a/app/assets/javascripts/notebook/cells/prompt.vue b/app/assets/javascripts/notebook/cells/prompt.vue index 3f1f239a806..1eeb61844a4 100644 --- a/app/assets/javascripts/notebook/cells/prompt.vue +++ b/app/assets/javascripts/notebook/cells/prompt.vue @@ -11,18 +11,26 @@ export default { required: false, default: 0, }, + showOutput: { + type: Boolean, + required: false, + default: true, + }, }, computed: { hasKeys() { return this.type !== '' && this.count; }, + showTypeText() { + return this.type && this.count && this.showOutput; + }, }, }; </script> <template> <div class="prompt"> - <span v-if="hasKeys"> {{ type }} [{{ count }}]: </span> + <span v-if="showTypeText"> {{ type }} [{{ count }}]: </span> </div> </template> diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue index 6a54d0b3823..e7056c03e4a 100644 --- a/app/assets/javascripts/notebook/index.vue +++ b/app/assets/javascripts/notebook/index.vue @@ -3,8 +3,8 @@ import { MarkdownCell, CodeCell } from './cells'; export default { components: { - 'code-cell': CodeCell, - 'markdown-cell': MarkdownCell, + CodeCell, + MarkdownCell, }, props: { notebook: { diff --git a/changelogs/unreleased/notebook-multiple-outputs.yml b/changelogs/unreleased/notebook-multiple-outputs.yml new file mode 100644 index 00000000000..38cc52c0634 --- /dev/null +++ b/changelogs/unreleased/notebook-multiple-outputs.yml @@ -0,0 +1,5 @@ +--- +title: Support multiple outputs in jupyter notebooks +merge_request: +author: +type: changed diff --git a/spec/javascripts/notebook/cells/output/html_spec.js b/spec/javascripts/notebook/cells/output/html_spec.js index bea62f54634..3ee404fb187 100644 --- a/spec/javascripts/notebook/cells/output/html_spec.js +++ b/spec/javascripts/notebook/cells/output/html_spec.js @@ -9,6 +9,8 @@ describe('html output cell', () => { return new Component({ propsData: { rawCode, + count: 0, + index: 0, }, }).$mount(); } diff --git a/spec/javascripts/notebook/cells/output/index_spec.js b/spec/javascripts/notebook/cells/output/index_spec.js index feab7ad4212..005569f1c2d 100644 --- a/spec/javascripts/notebook/cells/output/index_spec.js +++ b/spec/javascripts/notebook/cells/output/index_spec.js @@ -10,7 +10,7 @@ describe('Output component', () => { const createComponent = output => { vm = new Component({ propsData: { - output, + outputs: [].concat(output), count: 1, }, }); @@ -51,28 +51,21 @@ describe('Output component', () => { it('renders as an image', () => { expect(vm.$el.querySelector('img')).not.toBeNull(); }); - - it('does not render the prompt', () => { - expect(vm.$el.querySelector('.prompt span')).toBeNull(); - }); }); describe('html output', () => { - beforeEach(done => { + it('renders raw HTML', () => { createComponent(json.cells[4].outputs[0]); - setTimeout(() => { - done(); - }); - }); - - it('renders raw HTML', () => { expect(vm.$el.querySelector('p')).not.toBeNull(); - expect(vm.$el.textContent.trim()).toBe('test'); + expect(vm.$el.querySelectorAll('p').length).toBe(1); + expect(vm.$el.textContent.trim()).toContain('test'); }); - it('does not render the prompt', () => { - expect(vm.$el.querySelector('.prompt span')).toBeNull(); + it('renders multiple raw HTML outputs', () => { + createComponent([json.cells[4].outputs[0], json.cells[4].outputs[0]]); + + expect(vm.$el.querySelectorAll('p').length).toBe(2); }); }); @@ -88,10 +81,6 @@ describe('Output component', () => { it('renders as an svg', () => { expect(vm.$el.querySelector('svg')).not.toBeNull(); }); - - it('does not render the prompt', () => { - expect(vm.$el.querySelector('.prompt span')).toBeNull(); - }); }); describe('default to plain text', () => { |