Commit 6e03106d authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'ph/341046/widgetExtensionsLevel2Content' into 'master'

Added support for content level 2 in widget extensions

See merge request gitlab-org/gitlab!75744
parents 57f9eb78 17677c44
......@@ -16,6 +16,7 @@ import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants';
import StatusIcon from './status_icon.vue';
import Actions from './actions.vue';
import { generateText } from './utils';
export const LOADING_STATES = {
collapsedLoading: 'collapsedLoading',
......@@ -147,6 +148,9 @@ export default {
Sentry.captureException(e);
});
},
isArray(arr) {
return Array.isArray(arr);
},
appear(index) {
if (index === this.fullData.length - 1) {
this.showFade = false;
......@@ -157,6 +161,7 @@ export default {
this.showFade = true;
}
},
generateText,
},
EXTENSION_ICON_CLASS,
};
......@@ -177,7 +182,7 @@ export default {
<div class="gl-flex-grow-1">
<template v-if="isLoadingSummary">{{ widgetLoadingText }}</template>
<template v-else-if="hasFetchError">{{ widgetErrorText }}</template>
<div v-else v-safe-html="summary(collapsedData)"></div>
<div v-else v-safe-html="generateText(summary(collapsedData))"></div>
</div>
<actions
:widget="$options.label || $options.name"
......@@ -224,32 +229,59 @@ export default {
:class="{
'gl-border-b-solid gl-border-b-1 gl-border-gray-100': index !== fullData.length - 1,
}"
class="gl-display-flex gl-align-items-center gl-py-3 gl-pl-7"
class="gl-py-3 gl-pl-7"
data-testid="extension-list-item"
>
<status-icon v-if="data.icon" :icon-name="data.icon.name" :size="12" class="gl-pl-0" />
<gl-intersection-observer
:options="{ rootMargin: '100px', thresholds: 0.1 }"
class="gl-flex-wrap gl-display-flex gl-w-full"
@appear="appear(index)"
@disappear="disappear(index)"
>
<div
v-safe-html="data.text"
class="gl-mr-4 gl-display-flex gl-align-items-center"
></div>
<div v-if="data.link">
<gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
<div class="gl-w-full">
<div v-if="data.header" class="gl-mb-2">
<template v-if="isArray(data.header)">
<component
:is="headerI === 0 ? 'strong' : 'span'"
v-for="(header, headerI) in data.header"
:key="headerI"
v-safe-html="generateText(header)"
class="gl-display-block"
/>
</template>
<strong v-else v-safe-html="generateText(data.header)"></strong>
</div>
<div class="gl-display-flex">
<status-icon
v-if="data.icon"
:icon-name="data.icon.name"
:size="12"
class="gl-pl-0"
/>
<gl-intersection-observer
:options="{ rootMargin: '100px', thresholds: 0.1 }"
class="gl-w-full"
@appear="appear(index)"
@disappear="disappear(index)"
>
<div class="gl-flex-wrap gl-display-flex gl-w-full">
<div class="gl-mr-4 gl-display-flex gl-align-items-center">
<p v-safe-html="generateText(data.text)" class="gl-m-0"></p>
</div>
<div v-if="data.link">
<gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
</div>
<gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
{{ data.badge.text }}
</gl-badge>
<actions
:widget="$options.label || $options.name"
:tertiary-buttons="data.actions"
class="gl-ml-auto"
/>
</div>
<p
v-if="data.subtext"
v-safe-html="generateText(data.subtext)"
class="gl-m-0 gl-font-sm"
></p>
</gl-intersection-observer>
</div>
<gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
{{ data.badge.text }}
</gl-badge>
<actions
:widget="$options.label || $options.name"
:tertiary-buttons="data.actions"
class="gl-ml-auto"
/>
</gl-intersection-observer>
</div>
</li>
</smart-virtual-list>
<div
......
const TEXT_STYLES = {
success: {
start: '%{success_start}',
end: '%{success_end}',
},
danger: {
start: '%{danger_start}',
end: '%{danger_end}',
},
critical: {
start: '%{critical_start}',
end: '%{critical_end}',
},
same: {
start: '%{same_start}',
end: '%{same_end}',
},
strong: {
start: '%{strong_start}',
end: '%{strong_end}',
},
small: {
start: '%{small_start}',
end: '%{small_end}',
},
};
const getStartTag = (tag) => TEXT_STYLES[tag].start;
const textStyleTags = {
[getStartTag('success')]: '<span class="gl-font-weight-bold gl-text-green-500">',
[getStartTag('danger')]: '<span class="gl-font-weight-bold gl-text-red-500">',
[getStartTag('critical')]: '<span class="gl-font-weight-bold gl-text-red-800">',
[getStartTag('same')]: '<span class="gl-font-weight-bold gl-text-gray-700">',
[getStartTag('strong')]: '<span class="gl-font-weight-bold">',
[getStartTag('small')]: '<span class="gl-font-sm">',
};
export const generateText = (text) => {
if (typeof text !== 'string') return null;
return text
.replace(
new RegExp(
`(${Object.values(TEXT_STYLES)
.reduce((acc, i) => [...acc, ...Object.values(i)], [])
.join('|')})`,
'gi',
),
(replace) => {
const replacement = textStyleTags[replace];
// If the replacement tag ends with a `_end` then we can just return `</span>`
// unless we have a replacement, for cases were we want to change the HTML tag
if (!replacement && replace.endsWith('_end}')) {
return '</span>';
}
return replacement;
},
)
.replace(/%{([a-z]|_)+}/g, ''); // Filter out any tags we don't know about
};
......@@ -2,6 +2,7 @@
import { EXTENSION_ICONS } from '../constants';
import issuesCollapsedQuery from './issues_collapsed.query.graphql';
import issuesQuery from './issues.query.graphql';
import { n__, sprintf } from '~/locale';
export default {
// Give the extension a name
......@@ -20,7 +21,14 @@ export default {
// Small summary text to be displayed in the collapsed state
// Receives the collapsed data as an argument
summary(count) {
return 'Summary text<br/>Second line';
return sprintf(
n__(
'ciReport|Load performance test metrics detected %{strong_start}%{changesFound}%{strong_end} change',
'ciReport|Load performance test metrics detected %{strong_start}%{changesFound}%{strong_end} changes',
changesFound,
),
{ changesFound },
);
},
// Status icon to be used next to the summary text
// Receives the collapsed data as an argument
......@@ -57,9 +65,13 @@ export default {
.query({ query: issuesQuery, variables: { projectPath: targetProjectFullPath } })
.then(({ data }) => {
// Return some transformed data to be rendered in the expanded state
return data.project.issues.nodes.map((issue) => ({
return data.project.issues.nodes.map((issue, i) => ({
id: issue.id, // Required: The ID of the object
text: issue.title, // Required: The text to get used on each row
header: ['New', 'This is an %{strong_start}issue%{strong_end} row'],
text:
'%{critical_start}1 Critical%{critical_end}, %{danger_start}1 High%{danger_end}, and %{strong_start}1 Other%{strong_end}. %{small_start}Some smaller text%{small_end}', // Required: The text to get used on each row
subtext:
'Reported resource changes: %{strong_start}2%{strong_end} to add, 0 to change, 0 to delete', // Optional: The sub-text to get displayed below each rows main content
// Icon to get rendered on the side of each row
icon: {
// Required: Name maps to an icon in GitLabs SVG
......
......@@ -17,47 +17,30 @@ export default {
const changesFound = improved.length + degraded.length + same.length;
const text = sprintf(
n__(
'ciReport|Browser performance test metrics: %{strongStart}%{changesFound}%{strongEnd} change',
'ciReport|Browser performance test metrics: %{strongStart}%{changesFound}%{strongEnd} changes',
'ciReport|Browser performance test metrics: %{strong_start}%{changesFound}%{strong_end} change',
'ciReport|Browser performance test metrics: %{strong_start}%{changesFound}%{strong_end} changes',
changesFound,
),
{
changesFound,
strongStart: `<strong>`,
strongEnd: `</strong>`,
},
false,
);
const reportNumbers = [];
if (degraded.length > 0) {
reportNumbers.push(
`<strong class="gl-text-red-500">${sprintf(s__('ciReport|%{degradedNum} degraded'), {
degradedNum: degraded.length,
})}</strong>`,
);
}
if (same.length > 0) {
reportNumbers.push(
`<strong class="gl-text-gray-700">${sprintf(s__('ciReport|%{sameNum} same'), {
sameNum: same.length,
})}</strong>`,
);
}
if (improved.length > 0) {
reportNumbers.push(
`<strong class="gl-text-green-500">${sprintf(s__('ciReport|%{improvedNum} improved'), {
improvedNum: improved.length,
})}</strong>`,
);
}
const reportNumbersText = sprintf(
s__(
'ciReport|%{danger_start}%{degradedNum} degraded%{danger_end}, %{same_start}%{sameNum} same%{same_end}, and %{success_start}%{improvedNum} improved%{success_end}',
),
{
degradedNum: degraded.length,
sameNum: same.length,
improvedNum: improved.length,
},
);
return `${text}
<br>
${reportNumbers.join(', ')}
${reportNumbersText}
`;
},
statusIcon() {
......@@ -151,7 +134,7 @@ export default {
const text = sprintf(
s__(
'ciReport|%{prefix} %{strongStart}%{score}%{strongEnd} %{delta} %{deltaPercent} in %{path}',
'ciReport|%{prefix} %{strong_start}%{score}%{strong_end} %{delta} %{deltaPercent} in %{path}',
),
{
prefix,
......@@ -159,8 +142,6 @@ export default {
delta,
deltaPercent,
path,
strongStart: `<strong>`,
strongEnd: `</strong>`,
},
false,
);
......
......@@ -17,47 +17,27 @@ export default {
const changesFound = improved.length + degraded.length + same.length;
const text = sprintf(
n__(
'ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} change',
'ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} changes',
'ciReport|Load performance test metrics detected %{strong_start}%{changesFound}%{strong_end} change',
'ciReport|Load performance test metrics detected %{strong_start}%{changesFound}%{strong_end} changes',
changesFound,
),
{ changesFound },
);
const reportNumbersText = sprintf(
s__(
'ciReport|%{danger_start}%{degradedNum} degraded%{danger_end}, %{same_start}%{sameNum} same%{same_end}, and %{success_start}%{improvedNum} improved%{success_end}',
),
{
changesFound,
strongStart: `<strong>`,
strongEnd: `</strong>`,
degradedNum: degraded.length,
sameNum: same.length,
improvedNum: improved.length,
},
false,
);
const reportNumbers = [];
if (degraded.length > 0) {
reportNumbers.push(
`<strong class="gl-text-red-500">${sprintf(s__('ciReport|%{degradedNum} degraded'), {
degradedNum: degraded.length,
})}</strong>`,
);
}
if (same.length > 0) {
reportNumbers.push(
`<strong class="gl-text-gray-700">${sprintf(s__('ciReport|%{sameNum} same'), {
sameNum: same.length,
})}</strong>`,
);
}
if (improved.length > 0) {
reportNumbers.push(
`<strong class="gl-text-green-500">${sprintf(s__('ciReport|%{improvedNum} improved'), {
improvedNum: improved.length,
})}</strong>`,
);
}
return `${text}
<br>
${reportNumbers.join(', ')}
${reportNumbersText}
`;
},
statusIcon() {
......@@ -163,7 +143,7 @@ export default {
const prefix = metricData.score ? `${metricData.name}:` : metricData.name;
const score = metricData.score
? `<strong>${this.formatScore(metricData.score)}</strong>`
? `%{strong_start}${this.formatScore(metricData.score)}%{strong_end}`
: '';
const delta = metricData.delta ? `(${this.formatScore(metricData.delta)})` : '';
let deltaPercent = '';
......@@ -173,10 +153,8 @@ export default {
deltaPercent = `(${formattedChangeInPercent(oldScore, metricData.score)})`;
}
const text = `${prefix} ${score} ${delta} ${deltaPercent}`;
preparedMetricData.icon = icon;
preparedMetricData.text = text;
preparedMetricData.text = `${prefix} ${score} ${delta} ${deltaPercent}`;
return preparedMetricData;
},
......
......@@ -60,7 +60,7 @@ describe('Browser performance extension', () => {
await waitForPromises();
expect(wrapper.text()).toContain('Browser performance test metrics');
expect(wrapper.text()).toContain('2 degraded, 1 same, 1 improved');
expect(wrapper.text()).toContain('2 degraded, 1 same, and 1 improved');
});
it('should render info about fixed issues', async () => {
......
......@@ -60,7 +60,7 @@ describe('Load performance extension', () => {
await waitForPromises();
expect(wrapper.text()).toContain('Load performance test metrics detected 4 changes');
expect(wrapper.text()).toContain('1 degraded, 1 same, 2 improved');
expect(wrapper.text()).toContain('1 degraded, 1 same, and 2 improved');
});
it('should render info about fixed issues', async () => {
......
......@@ -40980,6 +40980,9 @@ msgstr ""
msgid "cannot merge"
msgstr ""
msgid "ciReport|%{danger_start}%{degradedNum} degraded%{danger_end}, %{same_start}%{sameNum} same%{same_end}, and %{success_start}%{improvedNum} improved%{success_end}"
msgstr ""
msgid "ciReport|%{degradedNum} degraded"
msgstr ""
......@@ -41010,7 +41013,7 @@ msgstr ""
msgid "ciReport|%{linkStartTag}Learn more about codequality reports %{linkEndTag}"
msgstr ""
msgid "ciReport|%{prefix} %{strongStart}%{score}%{strongEnd} %{delta} %{deltaPercent} in %{path}"
msgid "ciReport|%{prefix} %{strong_start}%{score}%{strong_end} %{delta} %{deltaPercent} in %{path}"
msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
......@@ -41058,8 +41061,8 @@ msgstr ""
msgid "ciReport|Browser performance test metrics: "
msgstr ""
msgid "ciReport|Browser performance test metrics: %{strongStart}%{changesFound}%{strongEnd} change"
msgid_plural "ciReport|Browser performance test metrics: %{strongStart}%{changesFound}%{strongEnd} changes"
msgid "ciReport|Browser performance test metrics: %{strong_start}%{changesFound}%{strong_end} change"
msgid_plural "ciReport|Browser performance test metrics: %{strong_start}%{changesFound}%{strong_end} changes"
msgstr[0] ""
msgstr[1] ""
......@@ -41153,8 +41156,8 @@ msgstr ""
msgid "ciReport|Load Performance"
msgstr ""
msgid "ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} change"
msgid_plural "ciReport|Load performance test metrics detected %{strongStart}%{changesFound}%{strongEnd} changes"
msgid "ciReport|Load performance test metrics detected %{strong_start}%{changesFound}%{strong_end} change"
msgid_plural "ciReport|Load performance test metrics detected %{strong_start}%{changesFound}%{strong_end} changes"
msgstr[0] ""
msgstr[1] ""
......
import { generateText } from '~/vue_merge_request_widget/components/extensions/utils';
describe('generateText', () => {
it.each`
text | expectedText
${'%{strong_start}Hello world%{strong_end}'} | ${'<span class="gl-font-weight-bold">Hello world</span>'}
${'%{success_start}Hello world%{success_end}'} | ${'<span class="gl-font-weight-bold gl-text-green-500">Hello world</span>'}
${'%{danger_start}Hello world%{danger_end}'} | ${'<span class="gl-font-weight-bold gl-text-red-500">Hello world</span>'}
${'%{critical_start}Hello world%{critical_end}'} | ${'<span class="gl-font-weight-bold gl-text-red-800">Hello world</span>'}
${'%{same_start}Hello world%{same_end}'} | ${'<span class="gl-font-weight-bold gl-text-gray-700">Hello world</span>'}
${'%{small_start}Hello world%{small_end}'} | ${'<span class="gl-font-sm">Hello world</span>'}
${'%{strong_start}%{danger_start}Hello world%{danger_end}%{strong_end}'} | ${'<span class="gl-font-weight-bold"><span class="gl-font-weight-bold gl-text-red-500">Hello world</span></span>'}
${'%{no_exist_start}Hello world%{no_exist_end}'} | ${'Hello world'}
${['array']} | ${null}
`('generates $expectedText from $text', ({ text, expectedText }) => {
expect(generateText(text)).toBe(expectedText);
});
});
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