Commit 026d483d authored by Phil Hughes's avatar Phil Hughes

Merge branch 'jivanvl-align-browser-performance-ux-designs' into 'master'

Refactor browser per widget to extension

See merge request gitlab-org/gitlab!71330
parents 5d42b170 3e712d3b
import { n__, s__, sprintf } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { formattedChangeInPercent } from '~/lib/utils/number_utils';
import { EXTENSION_ICONS } from '~/vue_merge_request_widget/constants';
export default {
name: 'WidgetBrowserPerformance',
props: ['browserPerformance'],
computed: {
summary() {
const { improved, degraded, same } = this.collapsedData;
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',
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>`,
);
}
return `${text}
<br>
${reportNumbers.join(', ')}
`;
},
statusIcon() {
if (this.collapsedData.degraded.length || this.collapsedData.same.length) {
return EXTENSION_ICONS.warning;
}
return EXTENSION_ICONS.success;
},
},
methods: {
fetchCollapsedData() {
const { head_path, base_path } = this.browserPerformance;
return Promise.all([this.fetchReport(head_path), this.fetchReport(base_path)]).then(
(values) => {
return this.compareBrowserPerformanceMetrics(values[0], values[1]);
},
);
},
fetchFullData() {
const { improved, degraded, same } = this.collapsedData;
return Promise.resolve([...improved, ...degraded, ...same]);
},
compareBrowserPerformanceMetrics(headMetrics, baseMetrics) {
const headMetricsIndexed = this.normalizeBrowserPerformanceMetrics(headMetrics);
const baseMetricsIndexed = this.normalizeBrowserPerformanceMetrics(baseMetrics);
const improved = [];
const degraded = [];
const same = [];
Object.keys(headMetricsIndexed).forEach((subject) => {
const subjectMetrics = headMetricsIndexed[subject];
Object.keys(subjectMetrics).forEach((metric) => {
const headMetricData = subjectMetrics[metric];
if (baseMetricsIndexed[subject] && baseMetricsIndexed[subject][metric]) {
const baseMetricData = baseMetricsIndexed[subject][metric];
const metricData = {
name: metric,
path: subject,
score: headMetricData.value,
delta: headMetricData.value - baseMetricData.value,
};
if (metricData.delta !== 0) {
const isImproved =
headMetricData.desiredSize === 'smaller'
? metricData.delta < 0
: metricData.delta > 0;
if (isImproved) {
improved.push(
this.prepareMetricData(metricData, {
name: EXTENSION_ICONS.success,
}),
);
} else {
degraded.push(
this.prepareMetricData(metricData, {
name: EXTENSION_ICONS.failed,
}),
);
}
} else {
same.push(
this.prepareMetricData(metricData, {
name: EXTENSION_ICONS.neutral,
}),
);
}
}
});
});
return { improved, degraded, same };
},
prepareMetricData(metricData, icon) {
const preparedMetricData = metricData;
const prefix = metricData.score ? `${metricData.name}:` : metricData.name;
const score = metricData.score ? `${this.formatScore(metricData.score)}` : '';
const delta = metricData.delta ? `(${this.formatScore(metricData.delta)})` : '';
const { path } = metricData;
let deltaPercent = '';
if (metricData.delta && metricData.score) {
const oldScore = parseFloat(metricData.score) - metricData.delta;
deltaPercent = `(${formattedChangeInPercent(oldScore, metricData.score)})`;
}
const text = sprintf(
s__(
'ciReport|%{prefix} %{strongStart}%{score}%{strongEnd} %{delta} %{deltaPercent} in %{path}',
),
{
prefix,
score,
delta,
deltaPercent,
path,
strongStart: `<strong>`,
strongEnd: `</strong>`,
},
false,
);
preparedMetricData.icon = icon;
preparedMetricData.text = text;
return preparedMetricData;
},
normalizeBrowserPerformanceMetrics(browserPerformanceData) {
const indexedSubjects = {};
browserPerformanceData.forEach(({ subject, metrics }) => {
const indexedMetrics = {};
metrics.forEach(({ name, ...data }) => {
indexedMetrics[name] = data;
});
indexedSubjects[subject] = indexedMetrics;
});
return indexedSubjects;
},
formatScore(value) {
if (Number(value) && !Number.isInteger(value)) {
return (Math.floor(parseFloat(value) * 100) / 100).toFixed(2);
}
return value;
},
fetchReport(endpoint) {
return axios.get(endpoint).then((res) => res.data);
},
},
};
......@@ -10,6 +10,7 @@ import MrWidgetJiraAssociationMissing from './components/states/mr_widget_jira_a
import MrWidgetPolicyViolation from './components/states/mr_widget_policy_violation.vue';
import MrWidgetGeoSecondaryNode from './components/states/mr_widget_secondary_geo_node.vue';
import loadPerformanceExtension from './extensions/load_performance';
import browserPerformanceExtension from './extensions/browser_performance';
export default {
components: {
......@@ -181,6 +182,7 @@ export default {
watch: {
hasBrowserPerformancePaths(newVal) {
if (newVal) {
this.registerBrowserPerformance();
this.fetchBrowserPerformance();
}
},
......@@ -190,17 +192,24 @@ export default {
this.fetchLoadPerformance();
}
},
shouldShowExtension() {
return (
window.gon.features.refactorMrWidgetsExtensions ||
window.gon.features.refactorMrWidgetsExtensionsUser
);
},
},
methods: {
registerLoadPerformance() {
const shouldShowExtension =
window.gon.features.refactorMrWidgetsExtensions ||
window.gon.features.refactorMrWidgetsExtensionsUser;
if (shouldShowExtension) {
if (this.shouldShowExtension) {
registerExtension(loadPerformanceExtension);
}
},
registerBrowserPerformance() {
if (this.shouldShowExtension) {
registerExtension(browserPerformanceExtension);
}
},
getServiceEndpoints(store) {
const base = CEWidgetOptions.methods.getServiceEndpoints(store);
......
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container';
import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
import browserPerformanceExtension from 'ee/vue_merge_request_widget/extensions/browser_performance';
import waitForPromises from 'helpers/wait_for_promises';
import { baseBrowserPerformance, headBrowserPerformance } from '../../mock_data';
describe('Browser performance extension', () => {
let wrapper;
let mock;
const DEFAULT_BROWSER_PERFORMANCE = {
head_path: 'head.json',
base_path: 'base.json',
};
const createComponent = () => {
wrapper = mount(extensionsContainer, {
propsData: {
mr: {
browserPerformance: {
...DEFAULT_BROWSER_PERFORMANCE,
},
},
},
});
};
beforeEach(() => {
createComponent();
mock = new MockAdapter(axios);
});
afterEach(() => {
wrapper.destroy();
});
describe('summary', () => {
it('should render info', async () => {
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.head_path).reply(200, headBrowserPerformance);
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.base_path).reply(200, baseBrowserPerformance);
registerExtension(browserPerformanceExtension);
await waitForPromises();
expect(wrapper.text()).toContain('Browser performance test metrics');
expect(wrapper.text()).toContain('2 degraded, 1 same, 1 improved');
});
it('should render info about fixed issues', async () => {
const head = [
{
metrics: [
{
name: 'Total Score',
value: 90,
desiredSize: 'larger',
},
],
},
];
const base = [
{
metrics: [
{
name: 'Total Score',
value: 80,
desiredSize: 'larger',
},
],
},
];
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.head_path).reply(200, head);
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.base_path).reply(200, base);
registerExtension(browserPerformanceExtension);
await waitForPromises();
expect(wrapper.text()).toContain('Browser performance test metrics: 1 change');
expect(wrapper.text()).toContain('1 improved');
});
it('should render info about added issues', async () => {
const head = [
{
metrics: [
{
name: 'Total Score',
value: 80,
desiredSize: 'larger',
},
],
},
];
const base = [
{
metrics: [
{
name: 'Total Score',
value: 90,
desiredSize: 'larger',
},
],
},
];
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.head_path).reply(200, head);
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.base_path).reply(200, base);
registerExtension(browserPerformanceExtension);
await waitForPromises();
expect(wrapper.text()).toContain('Browser performance test metrics: 1 change');
expect(wrapper.text()).toContain('1 degraded');
});
});
describe('expanded data', () => {
beforeEach(async () => {
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.head_path).reply(200, headBrowserPerformance);
mock.onGet(DEFAULT_BROWSER_PERFORMANCE.base_path).reply(200, baseBrowserPerformance);
registerExtension(browserPerformanceExtension);
await waitForPromises();
wrapper
.find('[data-testid="widget-extension"] [data-testid="toggle-button"]')
.trigger('click');
await nextTick();
});
it('shows the expanded list of text items', () => {
const listItems = wrapper.findAll('[data-testid="extension-list-item"]');
expect(listItems.at(0).text()).toBe('Speed Index: 1155 (-10) (-1%) in /some/path');
expect(listItems.at(1).text()).toBe('Total Score: 80 (-2) (-2%) in /some/path');
expect(listItems.at(2).text()).toBe('Transfer Size (KB): 1070.09 (5) (+0%) in /some/path');
});
});
});
......@@ -39745,6 +39745,9 @@ msgstr ""
msgid "ciReport|%{linkStartTag}Learn more about codequality reports %{linkEndTag}"
msgstr ""
msgid "ciReport|%{prefix} %{strongStart}%{score}%{strongEnd} %{delta} %{deltaPercent} in %{path}"
msgstr ""
msgid "ciReport|%{remainingPackagesCount} more"
msgstr ""
......@@ -39784,6 +39787,11 @@ 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"
msgstr[0] ""
msgstr[1] ""
msgid "ciReport|Browser performance test metrics: No changes"
msgstr ""
......
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