Dashboard Favorites: Dashboard star icon stuck in loading state on initial load (#114211)
Co-Author: Haris Rozajac <haris.rozajac12@gmail.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { render, screen, testWithFeatureToggles } from 'test/test-utils';
|
||||
import { render, screen, testWithFeatureToggles, waitFor } from 'test/test-utils';
|
||||
|
||||
import { GrafanaConfig, locationUtil } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { config, setBackendSrv } from '@grafana/runtime';
|
||||
import { setupMockServer } from '@grafana/test-utils/server';
|
||||
import { getFolderFixtures } from '@grafana/test-utils/unstable';
|
||||
@@ -110,5 +111,43 @@ describe('StarToolbarButton', () => {
|
||||
|
||||
expect(screen.queryByTestId(expectedTestId)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows spinner initially and transitions to empty star when loading completes with no starred items', async () => {
|
||||
setup(itemToStar);
|
||||
const button = screen.getByTestId(selectors.components.NavToolbar.markAsFavorite);
|
||||
|
||||
// Initially, button should be disabled (loading state)
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Wait for loading to complete
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(button).not.toBeDisabled();
|
||||
},
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
|
||||
// After loading, should show "Mark as favorite" (empty star state)
|
||||
expect(await findStarButton(itemToStar.title, false)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows spinner initially and transitions to filled star when loading completes with starred item', async () => {
|
||||
setup(existingStarredItem);
|
||||
const button = screen.getByTestId(selectors.components.NavToolbar.markAsFavorite);
|
||||
|
||||
// Initially, button should be disabled (loading state)
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Wait for loading to complete
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(button).not.toBeDisabled();
|
||||
},
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
|
||||
// After loading, should show "Unmark as favorite" (filled star state)
|
||||
expect(await findStarButton(existingStarredItem.title, true)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,7 +39,7 @@ export function StarToolbarButton({ title, group, kind, id, onStarChange }: Prop
|
||||
onStarChange?.(id, !isStarred);
|
||||
};
|
||||
|
||||
const iconProps = (() => {
|
||||
const iconProps = useMemo(() => {
|
||||
if (isLoading) {
|
||||
return { name: 'spinner', type: 'default' } as const;
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export function StarToolbarButton({ title, group, kind, id, onStarChange }: Prop
|
||||
return { name: 'favorite', type: 'mono' } as const;
|
||||
}
|
||||
return { name: 'star', type: 'default' } as const;
|
||||
})();
|
||||
}, [isLoading, isStarred]);
|
||||
|
||||
const tooltipAndLabel = (() => {
|
||||
return isStarred
|
||||
@@ -55,7 +55,7 @@ export function StarToolbarButton({ title, group, kind, id, onStarChange }: Prop
|
||||
: { tooltip: tooltips.star, label: isLoading ? undefined : tooltips.starWithTitle };
|
||||
})();
|
||||
|
||||
const icon = <Icon {...iconProps} size="lg" />;
|
||||
const icon = <Icon {...iconProps} size="lg" key={`${isLoading}-${isStarred}`} />;
|
||||
return (
|
||||
<ToolbarButton
|
||||
disabled={isLoading}
|
||||
|
||||
@@ -61,26 +61,45 @@ export const useStarredItems = (group: string, kind: string) => {
|
||||
const name = `user-${contextSrv.user.uid}`;
|
||||
const appPlatform = config.featureToggles.starsFromAPIServer;
|
||||
const legacyResponse = useLegacyGetStarsQuery(appPlatform ? skipToken : undefined);
|
||||
const appPlatformResponse = useListStarsQuery(!appPlatform ? skipToken : { fieldSelector: `metadata.name=${name}` });
|
||||
const queryArgs = !appPlatform ? skipToken : { fieldSelector: `metadata.name=${name}` };
|
||||
const appPlatformResponse = useListStarsQuery(queryArgs);
|
||||
|
||||
const appPlatformStarredItems = useMemo(() => {
|
||||
const { data } = appPlatformResponse;
|
||||
if (data) {
|
||||
const starredItems = appPlatformResponse.data?.items || [];
|
||||
if (!starredItems.length) {
|
||||
return [];
|
||||
}
|
||||
return starredItems[0]?.spec.resource.find((info) => info.group === group && info.kind === kind)?.names || [];
|
||||
const { data, isLoading, isUninitialized } = appPlatformResponse;
|
||||
|
||||
// If query hasn't been initiated yet or is still loading, return undefined to show loading state
|
||||
if (isUninitialized || isLoading) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
// If query completed but no data, return empty array
|
||||
if (!data) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const starredItems = appPlatformResponse.data?.items || [];
|
||||
if (!starredItems.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return starredItems[0]?.spec.resource.find((info) => info.group === group && info.kind === kind)?.names || [];
|
||||
}, [appPlatformResponse, group, kind]);
|
||||
|
||||
return appPlatform
|
||||
? {
|
||||
...appPlatformResponse,
|
||||
data: appPlatformStarredItems,
|
||||
}
|
||||
: legacyResponse;
|
||||
if (appPlatform) {
|
||||
return {
|
||||
...appPlatformResponse,
|
||||
data: appPlatformStarredItems,
|
||||
// Ensure isLoading is true when data is undefined (still loading or uninitialized)
|
||||
isLoading: appPlatformStarredItems === undefined ? true : appPlatformResponse.isLoading,
|
||||
};
|
||||
}
|
||||
|
||||
// For legacy response, ensure isLoading is true when query is uninitialized
|
||||
// RTK Query sets isLoading: false when uninitialized, but we need it to be true
|
||||
return {
|
||||
...legacyResponse,
|
||||
isLoading: legacyResponse.isUninitialized || legacyResponse.isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user