Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-18 00:09:20 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-18 00:09:20 +0300
commit3e20234984524c3ccfb09eace7b9d170cbcc32d7 (patch)
tree5586f86210db467613ba99040c96b617066de8c9 /app/assets/javascripts/vue_merge_request_widget
parent9a1066298169f8ebecacb9e55fe895f4f8962000 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget')
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue30
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js207
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/constants.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/issues.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js2
11 files changed, 243 insertions, 14 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue
index d878a1fa2e0..655ceb5f700 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue
@@ -26,6 +26,7 @@ export default {
},
methods: {
onClickAction(action) {
+ this.$emit('clickedAction', action);
if (action.onClick) {
action.onClick();
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
index 0e927d22a5a..4ba620da00a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
@@ -6,10 +6,8 @@ import {
GlTooltipDirective,
GlIntersectionObserver,
} from '@gitlab/ui';
-import { once } from 'lodash';
import * as Sentry from '@sentry/browser';
import { DynamicScroller, DynamicScrollerItem } from 'vendor/vue-virtual-scroller';
-import api from '~/api';
import { sprintf, s__, __ } from '~/locale';
import Poll from '~/lib/utils/poll';
import { normalizeHeaders } from '~/lib/utils/common_utils';
@@ -17,6 +15,7 @@ import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants';
import StatusIcon from './status_icon.vue';
import Actions from './actions.vue';
import ChildContent from './child_content.vue';
+import { createTelemetryHub } from './telemetry';
import { generateText } from './utils';
export const LOADING_STATES = {
@@ -27,6 +26,7 @@ export const LOADING_STATES = {
};
export default {
+ telemetry: true,
components: {
GlButton,
GlLoadingIcon,
@@ -50,6 +50,7 @@ export default {
showFade: false,
modalData: undefined,
modalName: undefined,
+ telemetry: null,
};
},
computed: {
@@ -132,20 +133,24 @@ export default {
}
},
},
+ created() {
+ if (this.$options.telemetry) {
+ this.telemetry = createTelemetryHub(this.$options.name);
+ }
+ },
mounted() {
this.loadCollapsedData();
+
+ this.telemetry?.viewed();
},
methods: {
- triggerRedisTracking: once(function triggerRedisTracking() {
- if (this.$options.expandEvent) {
- api.trackRedisHllUserEvent(this.$options.expandEvent);
- }
- }),
toggleCollapsed(e) {
if (this.isCollapsible && !e?.target?.closest('.btn:not(.btn-icon),a')) {
- this.isCollapsed = !this.isCollapsed;
+ if (this.isCollapsed) {
+ this.telemetry?.expanded({ type: this.statusIconName });
+ }
- this.triggerRedisTracking();
+ this.isCollapsed = !this.isCollapsed;
}
},
initExtensionMultiPolling() {
@@ -263,6 +268,11 @@ export default {
this.toggleCollapsed(e);
}
},
+ onClickedAction(action) {
+ if (action.fullReport) {
+ this.telemetry?.fullReportClicked();
+ }
+ },
generateText,
},
EXTENSION_ICON_CLASS,
@@ -300,6 +310,7 @@ export default {
<actions
:widget="$options.label || $options.name"
:tertiary-buttons="tertiaryActionsButtons"
+ @clickedAction="onClickedAction"
/>
<div
v-if="isCollapsible"
@@ -356,6 +367,7 @@ export default {
:widget-label="widgetLabel"
:modal-id="modalId"
:level="2"
+ @clickedAction="onClickedAction"
/>
</gl-intersection-observer>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
index 39a76e3b4c4..38f83a61b30 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
@@ -39,6 +39,9 @@ export default {
isArray(arr) {
return Array.isArray(arr);
},
+ onClickedAction(action) {
+ this.$emit('clickedAction', action);
+ },
generateText,
},
};
@@ -85,6 +88,7 @@ export default {
:widget="widgetLabel"
:tertiary-buttons="data.actions"
class="gl-ml-auto gl-pl-3"
+ @clickedAction="onClickedAction"
/>
</div>
<p
@@ -105,6 +109,7 @@ export default {
:modal-id="modalId"
:level="3"
data-testid="child-content"
+ @clickedAction="onClickedAction"
/>
</li>
</ul>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
index f22c6c756cd..f4fcf4c9571 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
@@ -16,6 +16,7 @@ export const registerExtension = (extension) => {
required: true,
},
},
+ telemetry: extension.telemetry,
i18n: extension.i18n,
expandEvent: extension.expandEvent,
enablePolling: extension.enablePolling,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js
new file mode 100644
index 00000000000..aec3a35f37c
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/telemetry.js
@@ -0,0 +1,207 @@
+import api from '~/api';
+import createEventHub from '~/helpers/event_hub_factory';
+import {
+ TELEMETRY_WIDGET_VIEWED,
+ TELEMETRY_WIDGET_EXPANDED,
+ TELEMETRY_WIDGET_FULL_REPORT_CLICKED,
+} from '../../constants';
+
+/*
+ * Additional events to send beyond the defaults for certain widget extensions
+ */
+const nonStandardEvents = {
+ codeQuality: {
+ uniqueUser: {
+ expand: ['i_testing_code_quality_widget_total'],
+ },
+ counter: {},
+ },
+ terraform: {
+ uniqueUser: {
+ expand: ['i_testing_terraform_widget_total'],
+ },
+ counter: {},
+ },
+ issues: {
+ uniqueUser: {
+ expand: ['i_testing_load_performance_widget_total'],
+ },
+ counter: {},
+ },
+ testReport: {
+ uniqueUser: {
+ expand: ['i_testing_summary_widget_total'],
+ },
+ counter: {},
+ },
+};
+
+function combineDeepArray(path, ...objects) {
+ const parts = path.split('.');
+ const allEntries = objects.reduce((entries, currentObject) => {
+ let expandedEntries = entries;
+ let traversed = currentObject;
+
+ parts.forEach((part) => {
+ traversed = traversed?.[part];
+ });
+
+ if (traversed) {
+ expandedEntries = [...entries, ...traversed];
+ }
+
+ return expandedEntries;
+ }, []);
+
+ return Array.from(new Set(allEntries));
+}
+
+function simplifyWidgetName(componentName) {
+ const noWidget = componentName.replace(/^Widget/, '');
+
+ return noWidget.charAt(0).toLowerCase() + noWidget.slice(1);
+}
+
+function baseRedisEventName(extensionName) {
+ const redisEventName = extensionName.replace(/([A-Z])/g, '_$1').toLowerCase();
+
+ return `i_merge_request_widget_${redisEventName}`;
+}
+
+function whenable(bus) {
+ return function when(event) {
+ return ({ execute, track, special }) => {
+ bus.$on(event, (busEvent) => {
+ track.forEach((redisEvent) => {
+ execute(redisEvent);
+ });
+
+ special?.({ event, execute, track, bus, busEvent });
+ });
+ };
+ };
+}
+
+function defaultBehaviorEvents({ bus, config }) {
+ const when = whenable(bus);
+ const isViewed = when(TELEMETRY_WIDGET_VIEWED);
+ const isExpanded = when(TELEMETRY_WIDGET_EXPANDED);
+ const fullReportIsClicked = when(TELEMETRY_WIDGET_FULL_REPORT_CLICKED);
+ const toHll = config?.uniqueUser || {};
+ const toCounts = config?.counter || {};
+ const user = api.trackRedisHllUserEvent.bind(api);
+ const count = api.trackRedisCounterEvent.bind(api);
+
+ if (toHll.view) {
+ isViewed({ execute: user, track: toHll.view });
+ }
+ if (toCounts.view) {
+ isViewed({ execute: count, track: toCounts.view });
+ }
+
+ if (toHll.expand) {
+ isExpanded({
+ execute: user,
+ track: toHll.expand,
+ special: ({ execute, track, busEvent }) => {
+ if (busEvent.type) {
+ track.forEach((event) => {
+ execute(`${event}_${busEvent.type}`);
+ });
+ }
+ },
+ });
+ }
+ if (toCounts.expand) {
+ isExpanded({
+ execute: count,
+ track: toCounts.expand,
+ special: ({ execute, track, busEvent }) => {
+ if (busEvent.type) {
+ track.forEach((event) => {
+ execute(`${event}_${busEvent.type}`);
+ });
+ }
+ },
+ });
+ }
+
+ if (toHll.clickFullReport) {
+ fullReportIsClicked({ execute: user, track: toHll.clickFullReport });
+ }
+ if (toCounts.clickFullReport) {
+ fullReportIsClicked({ execute: count, track: toCounts.clickFullReport });
+ }
+}
+
+function baseTelemetry(componentName) {
+ const simpleExtensionName = simplifyWidgetName(componentName);
+ const additionalNonStandard = nonStandardEvents[simpleExtensionName] || {};
+ /*
+ * Telemetry config format is:
+ * {
+ * TELEMETRY_TYPE: {
+ * BEHAVIOR: [ EVENT_NAME, ... ]
+ * }
+ * }
+ *
+ * Right now, there are currently configurations for these telemetry types:
+ * - uniqueUser is sent to RedisHLL
+ * - counter is sent to a regular Redis counter
+ */
+ const defaultTelemetry = {
+ uniqueUser: {
+ view: [`${baseRedisEventName(simpleExtensionName)}_view`],
+ expand: [`${baseRedisEventName(simpleExtensionName)}_expand`],
+ clickFullReport: [`${baseRedisEventName(simpleExtensionName)}_click_full_report`],
+ },
+ counter: {
+ view: [`${baseRedisEventName(simpleExtensionName)}_count_view`],
+ expand: [`${baseRedisEventName(simpleExtensionName)}_count_expand`],
+ clickFullReport: [`${baseRedisEventName(simpleExtensionName)}_count_click_full_report`],
+ },
+ };
+
+ return {
+ uniqueUser: {
+ view: combineDeepArray('uniqueUser.view', defaultTelemetry, additionalNonStandard),
+ expand: combineDeepArray('uniqueUser.expand', defaultTelemetry, additionalNonStandard),
+ clickFullReport: combineDeepArray(
+ 'uniqueUser.clickFullReport',
+ defaultTelemetry,
+ additionalNonStandard,
+ ),
+ },
+ counter: {
+ view: combineDeepArray('counter.view', defaultTelemetry, additionalNonStandard),
+ expand: combineDeepArray('counter.expand', defaultTelemetry, additionalNonStandard),
+ clickFullReport: combineDeepArray(
+ 'counter.clickFullReport',
+ defaultTelemetry,
+ additionalNonStandard,
+ ),
+ },
+ };
+}
+
+export function createTelemetryHub(componentName) {
+ const bus = createEventHub();
+ const config = baseTelemetry(componentName);
+
+ defaultBehaviorEvents({ bus, config });
+
+ return {
+ viewed() {
+ bus.$emit(TELEMETRY_WIDGET_VIEWED);
+ },
+ expanded({ type }) {
+ bus.$emit(TELEMETRY_WIDGET_EXPANDED, { type });
+ },
+ fullReportClicked() {
+ bus.$emit(TELEMETRY_WIDGET_FULL_REPORT_CLICKED);
+ },
+ /* I want a Record here: #{ ...config } // and then the comment would be: This is for debugging only, changing your reference to it does nothing. 😘 */
+ config: Object.freeze({ ...config }), // This is *intended* to be for debugging only, but it's pretty mutable, and it has references to live data in child props
+ bus,
+ };
+}
diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js
index 2c770392acf..c148a35209f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/constants.js
+++ b/app/assets/javascripts/vue_merge_request_widget/constants.js
@@ -166,6 +166,10 @@ export const EXTENSION_ICON_CLASS = {
export const EXTENSION_SUMMARY_FAILED_CLASS = 'gl-text-red-500';
export const EXTENSION_SUMMARY_NEUTRAL_CLASS = 'gl-text-gray-700';
+export const TELEMETRY_WIDGET_VIEWED = 'WIDGET_VIEWED';
+export const TELEMETRY_WIDGET_EXPANDED = 'WIDGET_EXPANDED';
+export const TELEMETRY_WIDGET_FULL_REPORT_CLICKED = 'WIDGET_FULL_REPORT_CLICKED';
+
export { STATE_MACHINE };
export const INVALID_RULES_DOCS_PATH = helpPagePath(
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js
index 168f10bd148..f14e80d0be6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js
@@ -6,6 +6,7 @@ import { EXTENSION_ICONS } from '../../constants';
export default {
name: 'WidgetAccessibility',
enablePolling: true,
+ telemetry: false,
i18n: {
loading: s__('Reports|Accessibility scanning results are being parsed'),
error: s__('Reports|Accessibility scanning failed loading results'),
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
index cea8df2484b..2477429af5b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
@@ -13,7 +13,6 @@ export default {
loading: s__('ciReport|Code Quality test metrics results are being parsed'),
error: s__('ciReport|Code Quality failed loading results'),
},
- expandEvent: 'i_testing_code_quality_widget_total',
computed: {
summary() {
const { newErrors, resolvedErrors, errorSummary } = this.collapsedData;
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js
index 6ca0ea9c4e7..a7aaa2f4476 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js
@@ -12,7 +12,6 @@ export default {
label: 'Issues',
loading: 'Loading issues...',
},
- expandEvent: 'i_testing_load_performance_widget_total',
// Add an array of props
// These then get mapped to values stored in the MR Widget store
props: ['targetProjectFullPath', 'conflictsDocsPath'],
@@ -45,7 +44,7 @@ export default {
console.log('Hello world');
},
},
- { text: 'Full report', href: this.conflictsDocsPath, target: '_blank' },
+ { text: 'Full report', href: this.conflictsDocsPath, target: '_blank', fullReport: true },
];
},
shouldCollapse() {
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js
index 8fcc4f818ec..6611aedcb07 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js
@@ -23,7 +23,6 @@ export default {
reportErrored: s__('Terraform|Generating the report caused an error.'),
fullLog: __('Full log'),
},
- expandEvent: 'i_testing_terraform_widget_total',
props: ['terraformReportsPath'],
computed: {
// Extension computed props
@@ -113,6 +112,7 @@ export default {
href: report.job_path,
text: this.$options.i18n.fullLog,
target: '_blank',
+ fullReport: true,
};
actions.push(action);
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
index 53c3d04b403..164bda33b95 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
@@ -16,7 +16,6 @@ export default {
name: 'WidgetTestSummary',
enablePolling: true,
i18n,
- expandEvent: 'i_testing_summary_widget_total',
props: ['testResultsPath', 'headBlobPath', 'pipeline'],
modalComponent: TestCaseDetails,
computed: {
@@ -50,6 +49,7 @@ export default {
text: this.$options.i18n.fullReport,
href: `${this.pipeline.path}/test_report`,
target: '_blank',
+ fullReport: true,
},
];
},