-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(Labels): Fix "Discarded by user" error in the UI #110
Conversation
@@ -10,7 +10,7 @@ export class SceneErrorState extends SceneObjectBase<SceneErrorStateState> { | |||
public static Component = ({ model }: SceneComponentProps<SceneErrorState>) => { | |||
const { message } = model.useState(); | |||
return ( | |||
<Alert title="Query error" severity="error"> | |||
<Alert title="Query error!" severity="error"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cosmetics...
</div> | ||
</Field> | ||
</> | ||
<Field label="Group by labels"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cosmetics as well
if (operator.value === OperatorKind.in) { | ||
return `${attribute.value}=~"${value.value}"`; | ||
} | ||
// if (operator.value === OperatorKind.in) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not strictly related to this PR, but as we don't support the in
operator, we shouldn't let this code being executed.
// we instantiate a new repository here to prevent unwanted interaction with the QueryBuilder | ||
// indeed, when entering the "idle" state, the QueryBuilder state machine will call cancelAllLoad() | ||
// that will abort any ApiClient in-flight request | ||
const labelsRepository = new LabelsRepository({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix
@@ -60,23 +60,23 @@ const spanNameFilter = { | |||
}, | |||
}; | |||
|
|||
const podIdFilter = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost LGTM
A couple of smaller questions/suggestion
// indeed, when entering the "idle" state, the QueryBuilder state machine will call cancelAllLoad() | ||
// that will abort any ApiClient in-flight request | ||
const labelsRepository = new LabelsRepository({ | ||
apiClient: new LabelsApiClient({ dataSourceUid: '' }), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the uid forced to be empty here, compared to
explore-profiles/src/shared/infrastructure/labels/labelsRepository.ts
Lines 115 to 118 in 4ee541a
export const labelsRepository = new LabelsRepository({ | |
apiClient: new LabelsApiClient(), | |
cacheClient: new MemoryCacheClient(), | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because they are different class of API clients. This one, used by LabelsDataSource
inherits from DataSourceProxyClient
, which requires a data source UID when instantiated. This allows us to switch the data source in runtime when the user selects different data source in the UI.
The other one inherits from ApiClient
, which determines the data source UID only when the user lands on the page for the 1st time. It's used by the (legacy) Comparison page where we don't need to switch the data source in runtime.
It's confusing, but it's temporal. Once we migrate the Comparison page, we'll only need the client inheriting from DataSourceProxyClient
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for explaining, that makes sense 👍
// that will abort any ApiClient in-flight request | ||
const labelsRepository = new LabelsRepository({ | ||
apiClient: new LabelsApiClient({ dataSourceUid: '' }), | ||
cacheClient: new MemoryCacheClient(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth reusing the cache between the two instances of this?
explore-profiles/src/shared/infrastructure/labels/labelsRepository.ts
Lines 115 to 118 in 4ee541a
export const labelsRepository = new LabelsRepository({ | |
apiClient: new LabelsApiClient(), | |
cacheClient: new MemoryCacheClient(), | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. By making the change, I actually realised that this fix is not the best we can do. I've just commited the new fix.
const pyroscopeQuery = `${profileMetricId}{service_name="${serviceName}"}`; | ||
|
||
const { from, to } = computeRoundedTimeRange(range as TimeRange); | ||
labelsRepository.setApiClient(new LabelsApiClient({ dataSourceUid })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix. We don't cache the API client anymore with DataSourceProxyClientBuilder
, so that when the query builder aborts a request, it will do it via its own API client.
We continue using the same instance of LabelsRepository
to be able to benefit from a shared cache.
@@ -60,18 +43,40 @@ export class LabelsDataSource extends RuntimeDataSource { | |||
}; | |||
} | |||
|
|||
async metricFindQuery(query: string, options: LegacyMetricFindQueryOptions): Promise<MetricFindValue[]> { | |||
getParams(options: LegacyMetricFindQueryOptions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some refactoring.
// TODO: remove this condition after migrating the legacy comparison pages to Scenes | ||
// because dataSourceUid will always be provided | ||
if (event.data.dataSourceUid) { | ||
labelsRepository.setApiClient(new LabelsApiClient({ dataSourceUid: event.data.dataSourceUid })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 2nd fix, we need to update the API client every time the data source changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Thanks for helping me understand this. I think this is ready to go.
✨ Description
Related issue(s):
-
This PR fixes an error that can occur when changing the data source or clicking on the main refresh button in the UI:
Additionally, this PR also fixes a potential issue with the query builder, which does not update its API client whenever the data source changes.
📖 Summary of the changes
The error comes from an unwanted interaction between the query builder and
LabelsDataSource
where the API request made byLabelsDataSource
to fetch label names is cancelled by the query builder state machine. This happens because the both share the same instance of theLabelsRepository
class, which in turn uses the same API client.The scenario is the following:
Whenever the user changes the data source:
GroupByVariable
gets its options updated viaLabelsDataSource
LabelsDataSource
,LabelsRepository
sets the API client corresponding to the new data sourceLabelsRepository
, this throws an abort error that bubbles up to the "Group by" UIThe solution is to make sure distinct instances of the API client are created by the query builder and by
LabelsDataSource
.See the diff tab for additional specific comments.
🧪 How to test?