Skip to content
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

Merged
merged 6 commits into from
Aug 6, 2024

Conversation

grafakus
Copy link
Contributor

@grafakus grafakus commented Aug 5, 2024

✨ 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:

image image

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 by LabelsDataSource to fetch label names is cancelled by the query builder state machine. This happens because the both share the same instance of the LabelsRepository class, which in turn uses the same API client.

The scenario is the following:

Whenever the user changes the data source:

  1. GroupByVariable gets its options updated via LabelsDataSource
  2. In LabelsDataSource, LabelsRepository sets the API client corresponding to the new data source
  3. the query builder state machine receives new input parameters, which in turn, causes the machine to transition to its idle state
  4. when entering this state, all in-flight requests are cancelled (see QueryBuilder/domain/actions.ts)
  5. because the same API client is used by LabelsRepository, this throws an abort error that bubbles up to the "Group by" UI

The 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?

  • Manually, after checking out this PR's branch

@github-actions github-actions bot added the fix label Aug 5, 2024
@@ -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">
Copy link
Contributor Author

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">
Copy link
Contributor Author

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) {
Copy link
Contributor Author

@grafakus grafakus Aug 5, 2024

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({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix

Copy link
Contributor

github-actions bot commented Aug 5, 2024

Unit test coverage

Lines Statements Branches Functions
Coverage: 11%
12.04% (464/3853) 9.35% (134/1432) 9.11% (107/1174)

@@ -60,23 +60,23 @@ const spanNameFilter = {
},
};

const podIdFilter = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments below

Copy link
Contributor

@simonswine simonswine left a 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: '' }),
Copy link
Contributor

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

export const labelsRepository = new LabelsRepository({
apiClient: new LabelsApiClient(),
cacheClient: new MemoryCacheClient(),
});

Copy link
Contributor Author

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.

Copy link
Contributor

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(),
Copy link
Contributor

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?

export const labelsRepository = new LabelsRepository({
apiClient: new LabelsApiClient(),
cacheClient: new MemoryCacheClient(),
});

Copy link
Contributor Author

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.

@grafakus grafakus marked this pull request as draft August 5, 2024 12:21
@grafakus grafakus marked this pull request as ready for review August 5, 2024 12:36
@grafakus grafakus changed the title fix(LabelsDataSource): Use a new independent LabelsRepository instance fix(LabelsDataSource): Fix "Discarded by user" error in the UI Aug 5, 2024
@grafakus grafakus changed the title fix(LabelsDataSource): Fix "Discarded by user" error in the UI fix(Labels): Fix "Discarded by user" error in the UI Aug 5, 2024
const pyroscopeQuery = `${profileMetricId}{service_name="${serviceName}"}`;

const { from, to } = computeRoundedTimeRange(range as TimeRange);
labelsRepository.setApiClient(new LabelsApiClient({ dataSourceUid }));
Copy link
Contributor Author

@grafakus grafakus Aug 5, 2024

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) {
Copy link
Contributor Author

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 }));
Copy link
Contributor Author

@grafakus grafakus Aug 5, 2024

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.

Copy link
Contributor

@simonswine simonswine left a 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.

@grafakus grafakus merged commit 2e9baab into main Aug 6, 2024
11 of 12 checks passed
@grafakus grafakus deleted the fix/labels-error-when-changing-ds branch August 6, 2024 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants