Commit 235ebffc authored by Denys Mishunov's avatar Denys Mishunov Committed by David O'Regan

Timely sanitization of initial Epic data

Epic, even though is using the issuable components,
doesn't follow the same pattern of sanitization of the
HTML data at the moment the data is read. This leads to
double-rendering of the Vue applications when description
content fetched with polling doesn't match the initial
one. Even though the difference is purely cosmetic
(stripped out line breaks in the initial description),
Vue still updates state and rerenders the view seeing
that the description is different.

This MR fixes this and unifies the way we sanitize output
for Issues and Epics
parent ae6f1b66
...@@ -3,7 +3,6 @@ import { GlIcon, GlIntersectionObserver } from '@gitlab/ui'; ...@@ -3,7 +3,6 @@ import { GlIcon, GlIntersectionObserver } from '@gitlab/ui';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import { __, s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import { sanitize } from '~/lib/dompurify';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import Poll from '~/lib/utils/poll'; import Poll from '~/lib/utils/poll';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -179,7 +178,7 @@ export default { ...@@ -179,7 +178,7 @@ export default {
const store = new Store({ const store = new Store({
titleHtml: this.initialTitleHtml, titleHtml: this.initialTitleHtml,
titleText: this.initialTitleText, titleText: this.initialTitleText,
descriptionHtml: sanitize(this.initialDescriptionHtml), descriptionHtml: this.initialDescriptionHtml,
descriptionText: this.initialDescriptionText, descriptionText: this.initialDescriptionText,
updatedAt: this.updatedAt, updatedAt: this.updatedAt,
updatedByName: this.updatedByName, updatedByName: this.updatedByName,
......
...@@ -4,13 +4,11 @@ import { sanitize } from '~/lib/dompurify'; ...@@ -4,13 +4,11 @@ import { sanitize } from '~/lib/dompurify';
// We currently load + parse the data from the issue app and related merge request // We currently load + parse the data from the issue app and related merge request
let cachedParsedData; let cachedParsedData;
export const parseIssuableData = () => { export const parseIssuableData = el => {
try { try {
if (cachedParsedData) return cachedParsedData; if (cachedParsedData) return cachedParsedData;
const initialDataEl = document.getElementById('js-issuable-app'); const parsedData = JSON.parse(el.dataset.initial);
const parsedData = JSON.parse(initialDataEl.dataset.initial);
parsedData.initialTitleHtml = sanitize(parsedData.initialTitleHtml); parsedData.initialTitleHtml = sanitize(parsedData.initialTitleHtml);
parsedData.initialDescriptionHtml = sanitize(parsedData.initialDescriptionHtml); parsedData.initialDescriptionHtml = sanitize(parsedData.initialDescriptionHtml);
......
...@@ -17,7 +17,8 @@ import initInviteMemberModal from '~/invite_member/init_invite_member_modal'; ...@@ -17,7 +17,8 @@ import initInviteMemberModal from '~/invite_member/init_invite_member_modal';
import { IssuableType } from '~/issuable_show/constants'; import { IssuableType } from '~/issuable_show/constants';
export default function() { export default function() {
const { issueType, ...issuableData } = parseIssuableData(); const initialDataEl = document.getElementById('js-issuable-app');
const { issueType, ...issuableData } = parseIssuableData(initialDataEl);
switch (issueType) { switch (issueType) {
case IssuableType.Incident: case IssuableType.Incident:
......
...@@ -5,6 +5,7 @@ import Cookies from 'js-cookie'; ...@@ -5,6 +5,7 @@ import Cookies from 'js-cookie';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store'; import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_vue/store';
import { parseIssuableData } from '~/issue_show/utils/parse_data';
import createStore from './store'; import createStore from './store';
import EpicApp from './components/epic_app.vue'; import EpicApp from './components/epic_app.vue';
...@@ -43,7 +44,7 @@ export default (epicCreate = false) => { ...@@ -43,7 +44,7 @@ export default (epicCreate = false) => {
} }
const epicMeta = convertObjectPropsToCamelCase(JSON.parse(el.dataset.meta), { deep: true }); const epicMeta = convertObjectPropsToCamelCase(JSON.parse(el.dataset.meta), { deep: true });
const epicData = JSON.parse(el.dataset.initial); const epicData = parseIssuableData(el);
// Collapse the sidebar on mobile screens by default // Collapse the sidebar on mobile screens by default
const bpBreakpoint = bp.getBreakpointSize(); const bpBreakpoint = bp.getBreakpointSize();
......
...@@ -30,7 +30,8 @@ describe('Issue show index', () => { ...@@ -30,7 +30,8 @@ describe('Issue show index', () => {
initialDescriptionHtml: '<svg onload=window.alert(1)>', initialDescriptionHtml: '<svg onload=window.alert(1)>',
}); });
const issuableData = parseData.parseIssuableData(); const initialDataEl = document.getElementById('js-issuable-app');
const issuableData = parseData.parseIssuableData(initialDataEl);
initIssuableApp(issuableData, createStore()); initIssuableApp(issuableData, createStore());
await waitForPromises(); await waitForPromises();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment