From 9a8766e03e97e33d8591ea3833ab02467a3089f8 Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Mon, 2 May 2022 09:43:38 -0400 Subject: [PATCH 01/20] Allow to create tables with JSON Convert JSON code blocks into tables if the code blocks have a table param Changelog: added --- .../behaviors/components/json_table.vue | 22 +++++++++++++ .../behaviors/markdown/render_gfm.js | 3 +- .../behaviors/markdown/render_json_table.js | 31 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/behaviors/components/json_table.vue create mode 100644 app/assets/javascripts/behaviors/markdown/render_json_table.js diff --git a/app/assets/javascripts/behaviors/components/json_table.vue b/app/assets/javascripts/behaviors/components/json_table.vue new file mode 100644 index 00000000000000..a6a76aee1360a8 --- /dev/null +++ b/app/assets/javascripts/behaviors/components/json_table.vue @@ -0,0 +1,22 @@ + + diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index c9ae370638358b..7716fed117320a 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -5,6 +5,7 @@ import { renderKroki } from './render_kroki'; import renderMath from './render_math'; import renderSandboxedMermaid from './render_sandboxed_mermaid'; import renderMetrics from './render_metrics'; +import { renderJSONTable } from './render_json_table'; // Render GitLab flavoured Markdown // @@ -15,7 +16,7 @@ $.fn.renderGFM = function renderGFM() { renderKroki(this.find('.js-render-kroki[hidden]').get()); renderMath(this.find('.js-render-math')); renderSandboxedMermaid(this.find('.js-render-mermaid')); - + renderJSONTable(document.querySelectorAll('[lang="json"][data-lang-params="table"]')); highlightCurrentUser(this.find('.gfm-project_member').get()); const issuablePopoverElements = this.find('.gfm-issue, .gfm-merge_request').get(); diff --git a/app/assets/javascripts/behaviors/markdown/render_json_table.js b/app/assets/javascripts/behaviors/markdown/render_json_table.js new file mode 100644 index 00000000000000..0d158984725643 --- /dev/null +++ b/app/assets/javascripts/behaviors/markdown/render_json_table.js @@ -0,0 +1,31 @@ +import Vue from 'vue'; +import JSONTable from '../components/json_table.vue'; + +const renderTable = (element) => { + const { fields, items } = JSON.parse(element.textContent); + const el = document.createElement('div'); + + element.insertAdjacentElement('afterend', el); + element.remove(); + + return new Vue({ + el, + components: { + JSONTable, + }, + render(h) { + return h(JSONTable, { + props: { + fields, + items, + }, + }); + }, + }); +}; + +export const renderJSONTable = (elements) => { + elements.forEach((el) => { + window.requestIdleCallback(() => renderTable(el)); + }); +}; -- GitLab From 86db432c6d5f970d8ca51e1ca1574fa783829877 Mon Sep 17 00:00:00 2001 From: Enrique Alcantara Date: Thu, 5 May 2022 09:19:46 -0400 Subject: [PATCH 02/20] Add test coverage --- .../behaviors/components/json_table_spec.js | 22 ++++++++++++++ .../markdown/render_json_table_spec.js | 29 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 spec/frontend/behaviors/components/json_table_spec.js create mode 100644 spec/frontend/behaviors/markdown/render_json_table_spec.js diff --git a/spec/frontend/behaviors/components/json_table_spec.js b/spec/frontend/behaviors/components/json_table_spec.js new file mode 100644 index 00000000000000..14abe440913068 --- /dev/null +++ b/spec/frontend/behaviors/components/json_table_spec.js @@ -0,0 +1,22 @@ +import { GlTableLite } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import JSONTable from '~/behaviors/components/json_table.vue'; + +describe('behaviors/components/json_table', () => { + let wrapper; + + const buildWrapper = ({ fields = [], items = [] } = {}) => { + wrapper = shallowMountExtended(JSONTable, { + propsData: { + fields, + items, + }, + }); + }; + + it('renders the component', () => { + buildWrapper(); + + expect(wrapper.findComponent(GlTableLite).exists()).toBe(true); + }); +}); diff --git a/spec/frontend/behaviors/markdown/render_json_table_spec.js b/spec/frontend/behaviors/markdown/render_json_table_spec.js new file mode 100644 index 00000000000000..d1547226eed11a --- /dev/null +++ b/spec/frontend/behaviors/markdown/render_json_table_spec.js @@ -0,0 +1,29 @@ +import { renderJSONTable } from '~/behaviors/markdown/render_json_table'; + +describe('behaviors/markdown/render_json_table', () => { + const tableFixture = { + fields: ['a', 'b', 'c'], + items: [ + { + a: 'a', + b: 'b', + c: 'c', + }, + ], + }; + + it('replaces pre code block with a table vue application', () => { + const pre = document.createElement('pre'); + + pre.textContent = JSON.stringify(tableFixture, null, 2); + + document.body.appendChild(pre); + + renderJSONTable(document.querySelectorAll('pre')); + + jest.runAllTimers(); + + expect(document.querySelectorAll('pre')).toHaveLength(0); + expect(document.querySelectorAll('table')).toHaveLength(1); + }); +}); -- GitLab From 3a8275e12efc8869be98f17457123d00aebd69ec Mon Sep 17 00:00:00 2001 From: Vishal Tak Date: Thu, 12 May 2022 15:05:15 +0530 Subject: [PATCH 03/20] Use GlTable. Add error message. Clean fields before rendering --- .../behaviors/components/json_table.vue | 26 +++++- .../behaviors/markdown/render_json_table.js | 12 ++- .../behaviors/components/json_table_spec.js | 83 +++++++++++++++++-- .../markdown/render_json_table_spec.js | 15 ++++ 4 files changed, 127 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/behaviors/components/json_table.vue b/app/assets/javascripts/behaviors/components/json_table.vue index a6a76aee1360a8..717d538ae058c3 100644 --- a/app/assets/javascripts/behaviors/components/json_table.vue +++ b/app/assets/javascripts/behaviors/components/json_table.vue @@ -1,9 +1,9 @@ diff --git a/app/assets/javascripts/behaviors/markdown/render_json_table.js b/app/assets/javascripts/behaviors/markdown/render_json_table.js index 0d158984725643..06936c7a3d7742 100644 --- a/app/assets/javascripts/behaviors/markdown/render_json_table.js +++ b/app/assets/javascripts/behaviors/markdown/render_json_table.js @@ -2,7 +2,16 @@ import Vue from 'vue'; import JSONTable from '../components/json_table.vue'; const renderTable = (element) => { - const { fields, items } = JSON.parse(element.textContent); + let rawData = []; + let errMsg = null; + + try { + rawData = JSON.parse(element.textContent); + } catch (e) { + errMsg = 'Unable to parse JSON'; + } + const fields = rawData.fields === undefined ? [] : rawData.fields; + const items = rawData.items === undefined ? [] : rawData.items; const el = document.createElement('div'); element.insertAdjacentElement('afterend', el); @@ -18,6 +27,7 @@ const renderTable = (element) => { props: { fields, items, + errMsg, }, }); }, diff --git a/spec/frontend/behaviors/components/json_table_spec.js b/spec/frontend/behaviors/components/json_table_spec.js index 14abe440913068..137eaafcd2f5e9 100644 --- a/spec/frontend/behaviors/components/json_table_spec.js +++ b/spec/frontend/behaviors/components/json_table_spec.js @@ -1,15 +1,16 @@ -import { GlTableLite } from '@gitlab/ui'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { GlTable } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import JSONTable from '~/behaviors/components/json_table.vue'; describe('behaviors/components/json_table', () => { let wrapper; - const buildWrapper = ({ fields = [], items = [] } = {}) => { - wrapper = shallowMountExtended(JSONTable, { + const buildWrapper = ({ fields = [], items = [], errMsg = undefined } = {}) => { + wrapper = mountExtended(JSONTable, { propsData: { fields, items, + errMsg, }, }); }; @@ -17,6 +18,78 @@ describe('behaviors/components/json_table', () => { it('renders the component', () => { buildWrapper(); - expect(wrapper.findComponent(GlTableLite).exists()).toBe(true); + expect(wrapper.findComponent(GlTable).exists()).toBe(true); + }); + + it('keep fields as it is when it is an array of strings', () => { + const tableFixture = { + fields: ['a', 'b', 'c'], + items: [ + { + a: 'a', + b: 'b', + c: 'c', + }, + ], + }; + const cleanedField = ['a', 'b', 'c']; + buildWrapper(tableFixture); + + expect(wrapper.findComponent(GlTable).vm.$options.parent.cleanedFields).toEqual(cleanedField); + }); + + it('clean fields when it is an array of objects', () => { + const tableFixture = { + fields: [ + { + key: 'a', + label: 'A', + extra: true, + }, + { + key: 'b', + label: 'B', + dummy: 1, + }, + { + key: 'c', + label: 'C', + }, + ], + items: [ + { + a: 'a', + b: 'b', + c: 'c', + }, + ], + }; + const cleanedField = [ + { + key: 'a', + label: 'A', + }, + { + key: 'b', + label: 'B', + }, + { + key: 'c', + label: 'C', + }, + ]; + buildWrapper(tableFixture); + + expect(wrapper.findComponent(GlTable).vm.$options.parent.cleanedFields).toEqual(cleanedField); + }); + + it('error is rendered', () => { + const errMsg = 'This is an error'; + buildWrapper({ errMsg }); + + const errMsgComponent = wrapper.find('#errMsg'); + expect(errMsgComponent.exists()).toBe(true); + expect(errMsgComponent.text()).toBe(errMsg); + expect(wrapper.findComponent(GlTable).exists()).toBe(false); }); }); diff --git a/spec/frontend/behaviors/markdown/render_json_table_spec.js b/spec/frontend/behaviors/markdown/render_json_table_spec.js index d1547226eed11a..5a5a876a6870a7 100644 --- a/spec/frontend/behaviors/markdown/render_json_table_spec.js +++ b/spec/frontend/behaviors/markdown/render_json_table_spec.js @@ -26,4 +26,19 @@ describe('behaviors/markdown/render_json_table', () => { expect(document.querySelectorAll('pre')).toHaveLength(0); expect(document.querySelectorAll('table')).toHaveLength(1); }); + + it('error is displayed when invalid JSON is provided', () => { + const pre = document.createElement('pre'); + + pre.textContent = JSON.stringify(tableFixture, null, 2).concat('.'); + + document.body.appendChild(pre); + + renderJSONTable(document.querySelectorAll('pre')); + + jest.runAllTimers(); + + expect(document.querySelectorAll('pre')).toHaveLength(0); + expect(document.querySelectorAll('h1')).toHaveLength(1); + }); }); -- GitLab From d28d2988dd7f5d47e3ca65acb81836109279c75b Mon Sep 17 00:00:00 2001 From: Vishal Tak Date: Thu, 12 May 2022 16:35:01 +0530 Subject: [PATCH 04/20] Use GlAlert for showing rendering errors --- .../javascripts/behaviors/components/json_table.vue | 11 ++++++++--- .../behaviors/markdown/render_json_table.js | 4 +++- spec/frontend/behaviors/components/json_table_spec.js | 6 ++---- .../behaviors/markdown/render_json_table_spec.js | 4 ++-- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/behaviors/components/json_table.vue b/app/assets/javascripts/behaviors/components/json_table.vue index 717d538ae058c3..720ee6503c5e0e 100644 --- a/app/assets/javascripts/behaviors/components/json_table.vue +++ b/app/assets/javascripts/behaviors/components/json_table.vue @@ -1,9 +1,10 @@ diff --git a/app/assets/javascripts/behaviors/markdown/render_json_table.js b/app/assets/javascripts/behaviors/markdown/render_json_table.js index 06936c7a3d7742..446645370d65e7 100644 --- a/app/assets/javascripts/behaviors/markdown/render_json_table.js +++ b/app/assets/javascripts/behaviors/markdown/render_json_table.js @@ -15,7 +15,9 @@ const renderTable = (element) => { const el = document.createElement('div'); element.insertAdjacentElement('afterend', el); - element.remove(); + if (errMsg === null) { + element.remove(); + } return new Vue({ el, diff --git a/spec/frontend/behaviors/components/json_table_spec.js b/spec/frontend/behaviors/components/json_table_spec.js index 137eaafcd2f5e9..92c8ae2af6418c 100644 --- a/spec/frontend/behaviors/components/json_table_spec.js +++ b/spec/frontend/behaviors/components/json_table_spec.js @@ -1,4 +1,4 @@ -import { GlTable } from '@gitlab/ui'; +import { GlTable, GlAlert } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import JSONTable from '~/behaviors/components/json_table.vue'; @@ -87,9 +87,7 @@ describe('behaviors/components/json_table', () => { const errMsg = 'This is an error'; buildWrapper({ errMsg }); - const errMsgComponent = wrapper.find('#errMsg'); - expect(errMsgComponent.exists()).toBe(true); - expect(errMsgComponent.text()).toBe(errMsg); + expect(wrapper.findComponent(GlAlert).exists()).toBe(true); expect(wrapper.findComponent(GlTable).exists()).toBe(false); }); }); diff --git a/spec/frontend/behaviors/markdown/render_json_table_spec.js b/spec/frontend/behaviors/markdown/render_json_table_spec.js index 5a5a876a6870a7..46d01c0688052a 100644 --- a/spec/frontend/behaviors/markdown/render_json_table_spec.js +++ b/spec/frontend/behaviors/markdown/render_json_table_spec.js @@ -38,7 +38,7 @@ describe('behaviors/markdown/render_json_table', () => { jest.runAllTimers(); - expect(document.querySelectorAll('pre')).toHaveLength(0); - expect(document.querySelectorAll('h1')).toHaveLength(1); + expect(document.querySelectorAll('pre')).toHaveLength(1); + expect(document.querySelectorAll('.gl-alert')).toHaveLength(1); }); }); -- GitLab From 05d506f60f07becb2fee68dde532182dde303b36 Mon Sep 17 00:00:00 2001 From: Vishal Tak Date: Fri, 20 May 2022 12:43:45 +0530 Subject: [PATCH 05/20] Add filter and sorting capabilities to table Improve error handling. Add ability to sort columns in tables and filter values. --- .../behaviors/components/json_table.vue | 27 ++++++-- .../behaviors/markdown/render_gfm.js | 4 +- .../behaviors/markdown/render_json_table.js | 15 ++-- locale/gitlab.pot | 6 ++ .../behaviors/components/json_table_spec.js | 68 +++++++------------ .../markdown/render_json_table_spec.js | 33 +++++++-- 6 files changed, 97 insertions(+), 56 deletions(-) diff --git a/app/assets/javascripts/behaviors/components/json_table.vue b/app/assets/javascripts/behaviors/components/json_table.vue index 720ee6503c5e0e..6492a40b5df81a 100644 --- a/app/assets/javascripts/behaviors/components/json_table.vue +++ b/app/assets/javascripts/behaviors/components/json_table.vue @@ -1,10 +1,11 @@ diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index 7716fed117320a..48d9d15387d2d8 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -16,7 +16,9 @@ $.fn.renderGFM = function renderGFM() { renderKroki(this.find('.js-render-kroki[hidden]').get()); renderMath(this.find('.js-render-math')); renderSandboxedMermaid(this.find('.js-render-mermaid')); - renderJSONTable(document.querySelectorAll('[lang="json"][data-lang-params="table"]')); + renderJSONTable( + Array.from(this.find('[lang="json"][data-lang-params="table"]').get()).map((e) => e.parentNode), + ); highlightCurrentUser(this.find('.gfm-project_member').get()); const issuablePopoverElements = this.find('.gfm-issue, .gfm-merge_request').get(); diff --git a/app/assets/javascripts/behaviors/markdown/render_json_table.js b/app/assets/javascripts/behaviors/markdown/render_json_table.js index 446645370d65e7..948c38a8e7052c 100644 --- a/app/assets/javascripts/behaviors/markdown/render_json_table.js +++ b/app/assets/javascripts/behaviors/markdown/render_json_table.js @@ -1,20 +1,26 @@ import Vue from 'vue'; +import { __ } from '~/locale'; import JSONTable from '../components/json_table.vue'; const renderTable = (element) => { + const prev = element.previousElementSibling; + if (prev && prev.classList.contains('js-json-table')) { + // To avoid rendering it multiple times + return {}; + } let rawData = []; let errMsg = null; try { rawData = JSON.parse(element.textContent); } catch (e) { - errMsg = 'Unable to parse JSON'; + errMsg = __('Unable to parse JSON'); + rawData = {}; } - const fields = rawData.fields === undefined ? [] : rawData.fields; - const items = rawData.items === undefined ? [] : rawData.items; + const { fields = [], items = [], filter } = rawData; const el = document.createElement('div'); - element.insertAdjacentElement('afterend', el); + element.insertAdjacentElement('beforebegin', el); if (errMsg === null) { element.remove(); } @@ -29,6 +35,7 @@ const renderTable = (element) => { props: { fields, items, + filter, errMsg, }, }); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d07818c19f55e1..522bd00a7a99cc 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -41250,6 +41250,9 @@ msgstr "" msgid "Type" msgstr "" +msgid "Type to search" +msgstr "" + msgid "U2F Devices (%{length})" msgstr "" @@ -41370,6 +41373,9 @@ msgstr "" msgid "Unable to load the merge request widget. Try reloading the page." msgstr "" +msgid "Unable to parse JSON" +msgstr "" + msgid "Unable to parse the vulnerability report's options." msgstr "" diff --git a/spec/frontend/behaviors/components/json_table_spec.js b/spec/frontend/behaviors/components/json_table_spec.js index 92c8ae2af6418c..5d27d316f044d6 100644 --- a/spec/frontend/behaviors/components/json_table_spec.js +++ b/spec/frontend/behaviors/components/json_table_spec.js @@ -1,15 +1,21 @@ -import { GlTable, GlAlert } from '@gitlab/ui'; +import { GlTable, GlAlert, GlFormInput } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import JSONTable from '~/behaviors/components/json_table.vue'; describe('behaviors/components/json_table', () => { let wrapper; - const buildWrapper = ({ fields = [], items = [], errMsg = undefined } = {}) => { + const buildWrapper = ({ + fields = [], + items = [], + filter = undefined, + errMsg = undefined, + } = {}) => { wrapper = mountExtended(JSONTable, { propsData: { fields, items, + filter, errMsg, }, }); @@ -24,13 +30,7 @@ describe('behaviors/components/json_table', () => { it('keep fields as it is when it is an array of strings', () => { const tableFixture = { fields: ['a', 'b', 'c'], - items: [ - { - a: 'a', - b: 'b', - c: 'c', - }, - ], + items: [{ a: 'a', b: 'b', c: 'c' }], }; const cleanedField = ['a', 'b', 'c']; buildWrapper(tableFixture); @@ -38,51 +38,33 @@ describe('behaviors/components/json_table', () => { expect(wrapper.findComponent(GlTable).vm.$options.parent.cleanedFields).toEqual(cleanedField); }); - it('clean fields when it is an array of objects', () => { + it('extra elements are removed from fields and defaults are setwhen it is an array of objects', () => { const tableFixture = { fields: [ - { - key: 'a', - label: 'A', - extra: true, - }, - { - key: 'b', - label: 'B', - dummy: 1, - }, - { - key: 'c', - label: 'C', - }, - ], - items: [ - { - a: 'a', - b: 'b', - c: 'c', - }, + { key: 'a', label: 'A', extra: true }, + { key: 'b', label: 'B', sortable: true, dummy: 1 }, + { key: 'c', label: 'C' }, ], + items: [{ a: 'a', b: 'b', c: 'c' }], }; const cleanedField = [ - { - key: 'a', - label: 'A', - }, - { - key: 'b', - label: 'B', - }, - { - key: 'c', - label: 'C', - }, + { key: 'a', label: 'A', sortable: false }, + { key: 'b', label: 'B', sortable: true }, + { key: 'c', label: 'C', sortable: false }, ]; buildWrapper(tableFixture); expect(wrapper.findComponent(GlTable).vm.$options.parent.cleanedFields).toEqual(cleanedField); }); + it('filter form input is rendered', () => { + const filter = true; + buildWrapper({ filter }); + + expect(wrapper.findComponent(GlTable).exists()).toBe(true); + expect(wrapper.findComponent(GlFormInput).exists()).toBe(true); + }); + it('error is rendered', () => { const errMsg = 'This is an error'; buildWrapper({ errMsg }); diff --git a/spec/frontend/behaviors/markdown/render_json_table_spec.js b/spec/frontend/behaviors/markdown/render_json_table_spec.js index 46d01c0688052a..a88ef4965bdf8d 100644 --- a/spec/frontend/behaviors/markdown/render_json_table_spec.js +++ b/spec/frontend/behaviors/markdown/render_json_table_spec.js @@ -13,13 +13,16 @@ describe('behaviors/markdown/render_json_table', () => { }; it('replaces pre code block with a table vue application', () => { + const parent = document.createElement('div'); const pre = document.createElement('pre'); pre.textContent = JSON.stringify(tableFixture, null, 2); - document.body.appendChild(pre); + parent.appendChild(pre); + document.body.innerHTML = ''; + document.body.appendChild(parent); - renderJSONTable(document.querySelectorAll('pre')); + renderJSONTable([parent]); jest.runAllTimers(); @@ -28,17 +31,39 @@ describe('behaviors/markdown/render_json_table', () => { }); it('error is displayed when invalid JSON is provided', () => { + const parent = document.createElement('div'); const pre = document.createElement('pre'); pre.textContent = JSON.stringify(tableFixture, null, 2).concat('.'); - document.body.appendChild(pre); + parent.appendChild(pre); + document.body.innerHTML = ''; + document.body.appendChild(parent); - renderJSONTable(document.querySelectorAll('pre')); + renderJSONTable([parent]); jest.runAllTimers(); expect(document.querySelectorAll('pre')).toHaveLength(1); expect(document.querySelectorAll('.gl-alert')).toHaveLength(1); }); + + it('form input is displayed when filter is set', () => { + const parent = document.createElement('div'); + const pre = document.createElement('pre'); + + pre.textContent = JSON.stringify({ ...tableFixture, filter: true }, null, 2); + + parent.appendChild(pre); + document.body.innerHTML = ''; + document.body.appendChild(parent); + + renderJSONTable([parent]); + + jest.runAllTimers(); + + expect(document.querySelectorAll('pre')).toHaveLength(0); + expect(document.querySelectorAll('table')).toHaveLength(1); + expect(document.querySelectorAll('.gl-form-input')).toHaveLength(1); + }); }); -- GitLab From cbb7343a6efb3de8cf611e789b93ce8a2fe9007c Mon Sep 17 00:00:00 2001 From: Vishal Tak Date: Wed, 29 Jun 2022 21:35:52 +0530 Subject: [PATCH 06/20] Differentiate between JSON Table and other tables --- .../behaviors/components/json_table.vue | 26 ++++++++++++++++--- .../behaviors/markdown/render_gfm.js | 1 + locale/gitlab.pot | 6 +++++ .../behaviors/components/json_table_spec.js | 3 ++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/behaviors/components/json_table.vue b/app/assets/javascripts/behaviors/components/json_table.vue index 6492a40b5df81a..b9cb282a1a4473 100644 --- a/app/assets/javascripts/behaviors/components/json_table.vue +++ b/app/assets/javascripts/behaviors/components/json_table.vue @@ -1,11 +1,16 @@ @@ -58,9 +68,17 @@ export default { {{ errMsg }} -
- - +
+ + {{ badgeText }} + + +
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index 48d9d15387d2d8..ee5c0fe5ef350c 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -19,6 +19,7 @@ $.fn.renderGFM = function renderGFM() { renderJSONTable( Array.from(this.find('[lang="json"][data-lang-params="table"]').get()).map((e) => e.parentNode), ); + highlightCurrentUser(this.find('.gfm-project_member').get()); const issuablePopoverElements = this.find('.gfm-issue, .gfm-merge_request').get(); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 522bd00a7a99cc..ca0f76a45937b9 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -22135,6 +22135,12 @@ msgstr "" msgid "Iteration|cannot be more than 500 years in the future" msgstr "" +msgid "JSON:Table" +msgstr "" + +msgid "JSON:Table is different from a Markdown Table" +msgstr "" + msgid "Jan" msgstr "" diff --git a/spec/frontend/behaviors/components/json_table_spec.js b/spec/frontend/behaviors/components/json_table_spec.js index 5d27d316f044d6..6db3326e1c179d 100644 --- a/spec/frontend/behaviors/components/json_table_spec.js +++ b/spec/frontend/behaviors/components/json_table_spec.js @@ -1,4 +1,4 @@ -import { GlTable, GlAlert, GlFormInput } from '@gitlab/ui'; +import { GlTable, GlAlert, GlFormInput, GlBadge } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import JSONTable from '~/behaviors/components/json_table.vue'; @@ -25,6 +25,7 @@ describe('behaviors/components/json_table', () => { buildWrapper(); expect(wrapper.findComponent(GlTable).exists()).toBe(true); + expect(wrapper.findComponent(GlBadge).exists()).toBe(true); }); it('keep fields as it is when it is an array of strings', () => { -- GitLab From 74746b0a2d609dfc7cbeaea4b1c95081b43da501 Mon Sep 17 00:00:00 2001 From: Vishal Tak Date: Fri, 1 Jul 2022 11:27:12 +0530 Subject: [PATCH 07/20] Add table tooltip and remove badge --- .../javascripts/behaviors/components/json_table.vue | 11 ++--------- locale/gitlab.pot | 3 --- spec/frontend/behaviors/components/json_table_spec.js | 3 +-- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/behaviors/components/json_table.vue b/app/assets/javascripts/behaviors/components/json_table.vue index b9cb282a1a4473..d340ec85e2bc28 100644 --- a/app/assets/javascripts/behaviors/components/json_table.vue +++ b/app/assets/javascripts/behaviors/components/json_table.vue @@ -1,13 +1,11 @@ diff --git a/app/assets/javascripts/behaviors/markdown/render_json_table.js b/app/assets/javascripts/behaviors/markdown/render_json_table.js index 07aa1910bcd193..fd3dad5450ae3e 100644 --- a/app/assets/javascripts/behaviors/markdown/render_json_table.js +++ b/app/assets/javascripts/behaviors/markdown/render_json_table.js @@ -1,47 +1,56 @@ import Vue from 'vue'; import { __ } from '~/locale'; +import { createAlert } from '~/flash'; import JSONTable from '../components/json_table.vue'; const renderTable = (element) => { const prev = element.previousElementSibling; - if (prev && prev.classList.contains('js-json-table')) { + if (prev?.classList.contains('js-json-table')) { // To avoid rendering it multiple times return {}; } - let rawData = []; - let errMsg = null; + const wrapper = document.createElement('div'); + wrapper.classList.add('js-json-table'); try { - rawData = JSON.parse(element.textContent); + const rawData = JSON.parse(element.textContent); + const { fields = [], items = [], filter, caption } = rawData; + const el = document.createElement('div'); + + wrapper.insertAdjacentElement('afterbegin', el); + element.innerHTML = ''; + element.insertAdjacentElement('beforebegin', wrapper); + + return new Vue({ + el, + components: { + JSONTable, + }, + render(h) { + return h(JSONTable, { + props: { + fields, + items, + filter, + caption, + }, + }); + }, + }); } catch (e) { - errMsg = __('Unable to parse JSON'); - rawData = {}; - } - const { fields = [], items = [], filter, caption } = rawData; - const el = document.createElement('div'); + const el = document.createElement('div'); + el.classList.add('js-json-table-error'); - element.insertAdjacentElement('beforebegin', el); - if (errMsg === null) { - element.remove(); - } + wrapper.insertAdjacentElement('afterbegin', el); + element.insertAdjacentElement('beforebegin', wrapper); - return new Vue({ - el, - components: { - JSONTable, - }, - render(h) { - return h(JSONTable, { - props: { - fields, - items, - filter, - errMsg, - caption, - }, - }); - }, - }); + return createAlert({ + message: __('Unable to parse JSON'), + variant: 'warning', + parent: wrapper, + containerSelector: '.js-json-table-error', + }); + } }; export const renderJSONTable = (elements) => { diff --git a/spec/frontend/behaviors/components/json_table_spec.js b/spec/frontend/behaviors/components/json_table_spec.js index 5d27d316f044d6..c7026e873c8d0b 100644 --- a/spec/frontend/behaviors/components/json_table_spec.js +++ b/spec/frontend/behaviors/components/json_table_spec.js @@ -1,4 +1,4 @@ -import { GlTable, GlAlert, GlFormInput } from '@gitlab/ui'; +import { GlTable, GlFormInput } from '@gitlab/ui'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import JSONTable from '~/behaviors/components/json_table.vue'; @@ -9,14 +9,14 @@ describe('behaviors/components/json_table', () => { fields = [], items = [], filter = undefined, - errMsg = undefined, + caption = undefined, } = {}) => { wrapper = mountExtended(JSONTable, { propsData: { fields, items, filter, - errMsg, + caption, }, }); }; @@ -24,7 +24,11 @@ describe('behaviors/components/json_table', () => { it('renders the component', () => { buildWrapper(); + const caption = wrapper.find('caption'); + expect(wrapper.findComponent(GlTable).exists()).toBe(true); + expect(caption.exists()).toBe(true); + expect(caption.text()).toBe('Generated with JSON data'); }); it('keep fields as it is when it is an array of strings', () => { @@ -64,12 +68,4 @@ describe('behaviors/components/json_table', () => { expect(wrapper.findComponent(GlTable).exists()).toBe(true); expect(wrapper.findComponent(GlFormInput).exists()).toBe(true); }); - - it('error is rendered', () => { - const errMsg = 'This is an error'; - buildWrapper({ errMsg }); - - expect(wrapper.findComponent(GlAlert).exists()).toBe(true); - expect(wrapper.findComponent(GlTable).exists()).toBe(false); - }); }); diff --git a/spec/frontend/behaviors/markdown/render_json_table_spec.js b/spec/frontend/behaviors/markdown/render_json_table_spec.js index a88ef4965bdf8d..2e75e4420627db 100644 --- a/spec/frontend/behaviors/markdown/render_json_table_spec.js +++ b/spec/frontend/behaviors/markdown/render_json_table_spec.js @@ -26,8 +26,14 @@ describe('behaviors/markdown/render_json_table', () => { jest.runAllTimers(); + const tableAsMatrix = (table) => + Array.from(table.querySelectorAll('tbody > tr')).map((tr) => + Array.from(tr.querySelectorAll('td')).map((x) => x.textContent), + ); + expect(document.querySelectorAll('pre')).toHaveLength(0); expect(document.querySelectorAll('table')).toHaveLength(1); + expect(tableAsMatrix(document.querySelector('table'))).toEqual([['a', 'b', 'c']]); }); it('error is displayed when invalid JSON is provided', () => { -- GitLab From a1230eac8fb57f952ef734d18ff5b208ce33a1e8 Mon Sep 17 00:00:00 2001 From: Vishal Tak Date: Mon, 18 Jul 2022 13:52:39 +0530 Subject: [PATCH 12/20] Make cleanedFields a computed property --- .../behaviors/components/json_table.vue | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/behaviors/components/json_table.vue b/app/assets/javascripts/behaviors/components/json_table.vue index e68ea620180e51..e5aee5234509ef 100644 --- a/app/assets/javascripts/behaviors/components/json_table.vue +++ b/app/assets/javascripts/behaviors/components/json_table.vue @@ -28,18 +28,7 @@ export default { }, }, data() { - const cleanedFields = this.fields.map((field) => { - if (typeof field === 'string') { - return field; - } - return { - key: field.key, - label: field.label, - sortable: field.sortable || false, - }; - }); return { - cleanedFields, filterInput: '', }; }, @@ -47,8 +36,17 @@ export default { hasFilter() { return this.filter; }, - columnCount() { - return this.cleanedFields.length; + cleanedFields() { + return this.fields.map((field) => { + if (typeof field === 'string') { + return field; + } + return { + key: field.key, + label: field.label, + sortable: field.sortable || false, + }; + }); }, }, }; -- GitLab From d7557f3bc1aec6a517852e2c3e2d0720c7964f7a Mon Sep 17 00:00:00 2001 From: Vishal Tak Date: Mon, 18 Jul 2022 14:10:36 +0530 Subject: [PATCH 13/20] Reduce explicit padding between table and caption --- app/assets/javascripts/behaviors/components/json_table.vue | 2 +- app/assets/stylesheets/framework/tables.scss | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/assets/javascripts/behaviors/components/json_table.vue b/app/assets/javascripts/behaviors/components/json_table.vue index e5aee5234509ef..23ba193a0f8117 100644 --- a/app/assets/javascripts/behaviors/components/json_table.vue +++ b/app/assets/javascripts/behaviors/components/json_table.vue @@ -64,7 +64,7 @@ export default { :items="items" :filter="filterInput" show-empty - class="json-table gl-mt-0!" + class="gl-mt-0!" >