Skip to content

Commit

Permalink
Load dashcard metadata in parallel with the query (#42953)
Browse files Browse the repository at this point in the history
* Split card data and query

* Split card data and query

* Split card data and query

* Fix loading state

* Postpone card check

* Postpone card check

* Postpone field check

* Postpone field check

* Postpone field check

* Postpone field check

* Postpone field check

* Add await

* Add await

* Add await

* Add await

* Fix tests

* Fix data loading

* Fix data loading

* Fix data loading

* Fix data loading

* Revert "Fix data loading"

This reverts commit 2b782b9.

* Fix data loading

* Fix test

* Fix test

* Fix embedding

* Fix xrays

* Fix xrays
  • Loading branch information
ranquild committed May 22, 2024
1 parent bdb006b commit a0a4a26
Show file tree
Hide file tree
Showing 16 changed files with 103 additions and 190 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@ describe("scenarios > dashboard > filters > nested questions", () => {
// Add multiple values (metabase#18113)
filterWidget().click();
popover().within(() => {
cy.findByText("Gizmo").click();
cy.findByText("Gadget").click();
cy.findByPlaceholderText("Enter some text")
.type("Gizmo")
.blur()
.type("Gadget")
.blur();
});

cy.button("Add filter").click();
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/metabase-lib/v1/parameters/utils/targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export function getParameterTargetField(
}

const fieldRef = target[1];
const query = question.query();
const metadata = question.metadata();

// native queries
Expand All @@ -60,8 +59,12 @@ export function getParameterTargetField(

if (isConcreteFieldReference(fieldRef)) {
const fieldId = fieldRef[1];
const tableId = Lib.sourceTableOrCardId(query);
return metadata.field(fieldId, tableId) ?? metadata.field(fieldId);
const resultMetadata = question.getResultMetadata();
const fieldMetadata = resultMetadata.find(field => field.id === fieldId);
return (
metadata.field(fieldId, fieldMetadata?.table_id) ??
metadata.field(fieldId)
);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ export function FieldValuesWidgetInner({
}}
onInputChange={onInputChange}
parseFreeformValue={parseFreeformValue}
updateOnInputBlur
/>
)}
</div>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/metabase/dashboard/actions/cards-typed.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { loadMetadataForDashcards } from "metabase/dashboard/actions/metadata";
import Questions from "metabase/entities/questions";
import {
DEFAULT_CARD_SIZE,
Expand Down Expand Up @@ -40,7 +41,6 @@ import {
setDashCardAttributes,
} from "./core";
import { cancelFetchCardData, fetchCardData } from "./data-fetching";
import { loadMetadataForDashboard } from "./metadata";
import { getExistingDashCards } from "./utils";

export type NewDashCardOpts = {
Expand Down Expand Up @@ -158,7 +158,7 @@ export const addCardToDashboard =
);

dispatch(fetchCardData(card, dashcard, { reload: true, clearCache: true }));
await dispatch(loadMetadataForDashboard([dashcard]));
await dispatch(loadMetadataForDashcards([dashcard]));
dispatch(autoWireParametersToNewCard({ dashcard_id: dashcardId }));
};

Expand Down Expand Up @@ -233,7 +233,7 @@ export const replaceCard =
const dashcard = getDashCardById(getState(), dashcardId);

dispatch(fetchCardData(card, dashcard, { reload: true, clearCache: true }));
await dispatch(loadMetadataForDashboard([dashcard]));
await dispatch(loadMetadataForDashcards([dashcard]));
dispatch(autoWireParametersToNewCard({ dashcard_id: dashcardId }));

dashboardId && trackQuestionReplaced(dashboardId);
Expand Down
21 changes: 2 additions & 19 deletions frontend/src/metabase/dashboard/actions/data-fetching-typed.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { denormalize, normalize, schema } from "normalizr";

import { loadMetadataForDashboard } from "metabase/dashboard/actions/metadata";
import {
getDashboardById,
getDashCardById,
getInitialSelectedTabId,
getParameterValues,
getQuestions,
getSelectedTabId,
} from "metabase/dashboard/selectors";
import {
expandInlineDashboard,
Expand Down Expand Up @@ -126,20 +123,6 @@ export const fetchDashboard = createAsyncThunk(

fetchDashboardCancellation = null;

if (dashboardType === "normal" || dashboardType === "transient") {
const selectedTabId =
getSelectedTabId(getState()) ?? getInitialSelectedTabId(result);

const cards =
selectedTabId == null
? result.dashcards
: result.dashcards.filter(
(c: DashboardCard) => c.dashboard_tab_id === selectedTabId,
);

await dispatch(loadMetadataForDashboard(cards));
}

const isUsingCachedResults = entities != null;
if (!isUsingCachedResults) {
// copy over any virtual cards from the dashcard to the underlying card/question
Expand All @@ -154,10 +137,10 @@ export const fetchDashboard = createAsyncThunk(
}

if (result.param_values) {
dispatch(addParamValues(result.param_values));
await dispatch(addParamValues(result.param_values));
}
if (result.param_fields) {
dispatch(addFields(result.param_fields));
await dispatch(addFields(result.param_fields));
}

const metadata = getMetadata(getState());
Expand Down
45 changes: 26 additions & 19 deletions frontend/src/metabase/dashboard/actions/data-fetching.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,19 @@ import {
getCurrentTabDashboardCards,
} from "../utils";

import { loadMetadataForDashboard } from "./metadata";
import { loadMetadataForDashcards } from "./metadata";

export const FETCH_DASHBOARD_CARD_DATA =
"metabase/dashboard/FETCH_DASHBOARD_CARD_DATA";
export const CANCEL_FETCH_DASHBOARD_CARD_DATA =
"metabase/dashboard/CANCEL_FETCH_DASHBOARD_CARD_DATA";

export const FETCH_DASHBOARD_CARD_METADATA =
"metabase/dashboard/FETCH_DASHBOARD_CARD_METADATA";

export const FETCH_CARD_DATA = "metabase/dashboard/FETCH_CARD_DATA";
export const FETCH_CARD_DATA_PENDING =
"metabase/dashboard/FETCH_CARD_DATA/pending";

export const FETCH_CARD_QUERY = "metabase/dashboard/FETCH_CARD_QUERY";

export const CANCEL_FETCH_CARD_DATA =
"metabase/dashboard/CANCEL_FETCH_CARD_DATA";

Expand Down Expand Up @@ -118,7 +117,7 @@ const loadingComplete = createThunkAction(

export const fetchCardData = createThunkAction(
FETCH_CARD_DATA,
function (card, dashcard, { reload, clearCache, ignoreCache } = {}) {
function (card, dashcard, options) {
return async function (dispatch, getState) {
dispatch({
type: FETCH_CARD_DATA_PENDING,
Expand All @@ -128,6 +127,28 @@ export const fetchCardData = createThunkAction(
},
});

const requests = [dispatch(fetchCardQuery(card, dashcard, options))];

const dashboardType = getDashboardType(dashcard.dashboard_id);
if (dashboardType === "normal" || dashboardType === "transient") {
requests.push(dispatch(loadMetadataForDashcards([dashcard])));
}

const [{ payload }] = await Promise.all(requests);

return {
dashcard_id: dashcard.id,
card_id: card.id,
...payload,
};
};
},
);

export const fetchCardQuery = createThunkAction(
FETCH_CARD_QUERY,
function (card, dashcard, { reload, clearCache, ignoreCache } = {}) {
return async function (dispatch, getState) {
// If the dataset_query was filtered then we don't have permission to view this card, so
// shortcircuit and return a fake 403
if (!card.dataset_query) {
Expand Down Expand Up @@ -376,20 +397,6 @@ export const fetchDashboardCardData =
}
};

export const fetchDashboardCardMetadata = createThunkAction(
FETCH_DASHBOARD_CARD_METADATA,
() => async (dispatch, getState) => {
const allDashCards = getDashboardComplete(getState()).dashcards;
const selectedTabId = getSelectedTabId(getState());

const cards = allDashCards.filter(
dc =>
selectedTabId !== undefined && dc.dashboard_tab_id === selectedTabId,
);
await dispatch(loadMetadataForDashboard(cards));
},
);

export const reloadDashboardCards = () => async (dispatch, getState) => {
const dashboard = getDashboardComplete(getState());

Expand Down
108 changes: 1 addition & 107 deletions frontend/src/metabase/dashboard/actions/data-fetching.unit.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import fetchMock from "fetch-mock";

import { getStore } from "__support__/entities-store";
import {
setupDashboardsEndpoints,
Expand All @@ -8,18 +6,10 @@ import {
import { createMockEntitiesState } from "__support__/store";
import { Api } from "metabase/api";
import {
createMockCard,
createMockDashboard,
createMockDashboardCard,
createMockDashboardTab,
createMockSettings,
createMockStructuredDatasetQuery,
} from "metabase-types/api/mocks";
import {
createSampleDatabase,
ORDERS_ID,
PRODUCTS_ID,
} from "metabase-types/api/mocks/presets";
import { createSampleDatabase } from "metabase-types/api/mocks/presets";
import { createMockDashboardState } from "metabase-types/store/mocks";

import { dashboardReducers } from "../reducers";
Expand Down Expand Up @@ -54,102 +44,6 @@ function setup({ dashboards = [] }) {
}

describe("fetchDashboard", () => {
it("should fetch metadata for all cards when there are no tabs", async () => {
const dashboard = createMockDashboard({
dashcards: [
createMockDashboardCard({
card: createMockCard({
dataset_query: createMockStructuredDatasetQuery({
query: {
"source-table": PRODUCTS_ID,
},
}),
}),
}),
createMockDashboardCard({
card: createMockCard({
dataset_query: createMockStructuredDatasetQuery({
query: {
"source-table": ORDERS_ID,
},
}),
}),
}),
],
});
const store = setup({
dashboards: [dashboard],
});

await store.dispatch(
fetchDashboard({
dashId: dashboard.id,
queryParams: {},
options: {},
}),
);

expect(
fetchMock.calls(`path:/api/table/${PRODUCTS_ID}/query_metadata`),
).toHaveLength(1);
expect(
fetchMock.calls(`path:/api/table/${ORDERS_ID}/query_metadata`),
).toHaveLength(1);
});

it("should fetch metadata for cards on the first tab when there are tabs", async () => {
const dashboard = createMockDashboard({
dashcards: [
createMockDashboardCard({
card: createMockCard({
dataset_query: createMockStructuredDatasetQuery({
query: {
"source-table": PRODUCTS_ID,
},
}),
}),
dashboard_tab_id: 1,
}),
createMockDashboardCard({
card: createMockCard({
dataset_query: createMockStructuredDatasetQuery({
query: {
"source-table": ORDERS_ID,
},
}),
}),
dashboard_tab_id: 2,
}),
],
tabs: [
createMockDashboardTab({
id: 1,
}),
createMockDashboardTab({
id: 2,
}),
],
});
const store = setup({
dashboards: [dashboard],
});

await store.dispatch(
fetchDashboard({
dashId: dashboard.id,
queryParams: {},
options: {},
}),
);

expect(
fetchMock.calls(`path:/api/table/${PRODUCTS_ID}/query_metadata`),
).toHaveLength(1);
expect(
fetchMock.calls(`path:/api/table/${ORDERS_ID}/query_metadata`),
).toHaveLength(0);
});

it("should cancel previous dashboard fetch when a new one is initiated (metabase#35959)", async () => {
const store = setup({
dashboards: [
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/metabase/dashboard/actions/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { loadMetadataForCard } from "metabase/questions/actions";

import { isVirtualDashCard } from "../utils";

export const loadMetadataForDashboard = dashCards => async dispatch => {
const cards = dashCards
export const loadMetadataForDashcards = dashcards => async dispatch => {
const cards = dashcards
.filter(dc => !isVirtualDashCard(dc)) // exclude text cards
.flatMap(dc => [dc.card].concat(dc.series || []));

await Promise.all([
dispatch(loadMetadataForCards(cards)),
dispatch(loadMetadataForLinkedTargets(dashCards)),
dispatch(loadMetadataForLinkedTargets(dashcards)),
]);
};

Expand Down
5 changes: 4 additions & 1 deletion frontend/src/metabase/dashboard/actions/navigation.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { loadMetadataForDashcards } from "metabase/dashboard/actions/metadata";
import { createThunkAction } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { getParametersMappedToDashcard } from "metabase/parameters/utils/dashboards";
Expand Down Expand Up @@ -43,7 +44,9 @@ export const NAVIGATE_TO_NEW_CARD = "metabase/dashboard/NAVIGATE_TO_NEW_CARD";
export const navigateToNewCardFromDashboard = createThunkAction(
NAVIGATE_TO_NEW_CARD,
({ nextCard, previousCard, dashcard, objectId }) =>
(dispatch, getState) => {
async (dispatch, getState) => {
await dispatch(loadMetadataForDashcards([dashcard]));

const metadata = getMetadata(getState());
const { dashboardId, dashboards, parameterValues } = getState().dashboard;
const dashboard = dashboards[dashboardId];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled from "@emotion/styled";
import EntityMenu from "metabase/components/EntityMenu";
import { color, lighten } from "metabase/lib/colors";

export const CardMenuRoot = styled(EntityMenu)`
export const CardEntityMenu = styled(EntityMenu)`
display: flex;
align-items: center;
margin: 0 0 0 0.5rem;
Expand Down
Loading

0 comments on commit a0a4a26

Please sign in to comment.