[MR Widget Eng] - Add ability to poll nested expanded content
Context
This issue builds on #361286 (closed)
Using two different endpoints for collapsed/uncollapsed state
This is done for performance optimizations. The idea being that we need much less data to provide a collapsed summary. For some widgets the collapsed/uncollapsed state use the endpoint.
For others, that are optimized like License Compliance, we have 2 endpoints. All of the ~"group::secure" widgets suffer from the 204 - No Content
problem as described in #361286 (closed)
Problem
The Widget extension has polling only for the collapsed endpoint. The assumption being that if the data is available in the collapsed state, then we should have data ready for the expanded state.
The problem with ~"group::secure" widgets is that we parse report artifacts after a successful pipeline run. The parsed results are cached. The caching layer works at the endpoint layer. By having two different endpoints, the collapsed endpoint will eventually show a 200 - OK with data populated. This assumes we solve #361286 (closed). That result is cached.
When we expand the widget, we hit the fullData endpoint. Because it's a different endpoint, the results are a cache miss
, and the reports are then re-generated. We then end up with the 204 - No Content
#361286 (closed) problem all over again.
Problem Workflow
Initial page load -> initial widget load -> 204 - No Content Response - Continue to Poll until 200 OK -> then show widget summary -> Expand widget -> Attempt to fetch fullData endpoint -> Another 204 - No Content -> Another exception caught because we have no data -> Widget errors out
Solution
Allow for something like enablePollingFullData
so we can implement the same polling logic as the enablePolling
flag for the collapsed endpoints. Handle 204/200 or any other successful status code
Gotchas
- We need to solve how we want to handle polling in for
204 - No Content
#361286 (closed). It blocks this issue
Implementation Plan
Based off the work in !87107 (merged)
In: app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
- Leverage the existing
enablePolling: true
setting. - Update
loadAllData() {
if (this.hasFullData) return;
this.loadingState = LOADING_STATES.expandedLoading;
this.fetchFullData(this.$props)
.then((data) => {
this.loadingState = null;
this.fullData = data.map((x, i) => ({ id: i, ...x }));
})
.catch((e) => {
this.loadingState = LOADING_STATES.expandedError;
Sentry.captureException(e);
});
},
to something like
loadAllData() {
if (this.hasFullData) return;
this.loadingState = LOADING_STATES.expandedLoading;
if (this.$options.enablePolling) {
if (this.fetchFullMultiData) {
this.initExtensionFullDataMultiPolling();
} else {
this.initExtensionFullDataPolling();
}
} else {
this.fetchFullData(this.$props)
.then((data) => {
this.loadingState = null;
this.fullData = data.map((x, i) => ({ id: i, ...x }));
})
.catch((e) => {
this.loadingState = LOADING_STATES.expandedError;
Sentry.captureException(e);
});
}
},
- Implement
initExtensionFullDataMultiPolling
andinitExtensionFullDataPolling
as something like:
initExtensionFullDataMultiPolling() {
const allData = [];
const requests = this.fetchMultiData();
requests.forEach((request) => {
const poll = new Poll({
resource: {
fetchData: () => request(this.$props),
},
method: 'fetchData',
successCallback: (response) => {
const headers = normalizeHeaders(response.headers);
if (typeof headers['POLL-INTERVAL'] === 'undefined') {
poll.stop();
allData.push(response.data);
}
if (allData.length === requests.length) {
this.setFullData(allData);
}
},
errorCallback: (e) => {
poll.stop();
this.loadingState = LOADING_STATES.expandedError;
},
});
poll.makeRequest();
});
},
initExtensionFulLDataPolling() {
const poll = new Poll({
resource: {
fetchData: () => this.fetchFullData(this.$props),
},
method: 'fetchData',
successCallback: ({ data }) => {
if (Object.keys(data).length > 0) {
poll.stop();
this.setFullData(data);
}
},
errorCallback: (e) => {
poll.stop();
this.loadingState = LOADING_STATES.expandedError;
},
});
poll.makeRequest();
},
- Implement
setFullData
as something like:
setFullData(data) {
this.loadingState = null;
this.fullData = data.map((x, i) => ({ id: i, ...x }));
},
-
Attempt to refactor and generalize the
initExtensionFullDataMultiPolling
,initExtensionFullDataMultiPolling
,initExtensionFulLDataPolling
,initExtensionPolling
functions to reduce code duplication -
Update existing components with
enablePolling: true
behave correctly with the changes in the implementation plan by passing the full response object instead of the data only. Update unit tests as well
app/assets/javascripts/vue_merge_request_widget/extensions/accessibility/index.js
app/assets/javascripts/vue_merge_request_widget/extensions/test_report/index.js
ee/app/assets/javascripts/vue_merge_request_widget/extensions/metrics/index.js