Skip to content
Snippets Groups Projects
Unverified Commit 9b8ac708 authored by Alexander Polyankin's avatar Alexander Polyankin Committed by GitHub
Browse files

Fix not being able to open a card from a dashboard with sandboxing (#29758)

parent 163b6a71
No related branches found
No related tags found
No related merge requests found
import { describeEE, restore, visitDashboard } from "e2e/support/helpers";
import { SAMPLE_DATABASE } from "e2e/support/cypress_sample_database";
const { PRODUCTS_ID, PRODUCTS } = SAMPLE_DATABASE;
describeEE("issue 29076", () => {
beforeEach(() => {
restore();
cy.intercept("/api/dashboard/*/dashcard/*/card/*/query").as("cardQuery");
cy.signInAsAdmin();
cy.sandboxTable({
table_id: PRODUCTS_ID,
attribute_remappings: {
attr_uid: ["dimension", ["field", PRODUCTS.ID, null]],
},
});
cy.signInAsSandboxedUser();
});
it("should be able to drilldown to a saved question in a dashboard with sandboxing (metabase#29076)", () => {
visitDashboard(1);
cy.wait("@cardQuery");
cy.findByText("Orders").click();
cy.wait("@cardQuery");
cy.findByText("Visualization").should("be.visible");
});
});
......@@ -1378,7 +1378,7 @@ class QuestionInner {
if (this.isStructured()) {
const questionWithParameters = this.setParameters(parameters);
if (!this.query().readOnly()) {
if (this.query().isEditable()) {
return questionWithParameters
.setParameterValues(parameterValues)
._convertParametersToMbql()
......
import { loadMetadataForQueries } from "metabase/redux/metadata";
import Questions from "metabase/entities/questions";
import { getMetadata } from "metabase/selectors/metadata";
import { loadMetadataForQueries } from "metabase/redux/metadata";
import { getLinkTargets } from "metabase/lib/click-behavior";
import Question from "metabase-lib/Question";
import { isVirtualDashCard } from "../utils";
export const loadMetadataForDashboard = dashCards => (dispatch, getState) => {
export const loadMetadataForDashboard = 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)),
]);
};
const loadMetadataForCards = cards => (dispatch, getState) => {
const metadata = getMetadata(getState());
const questions = dashCards
.filter(dc => !isVirtualDashCard(dc) && dc.card.dataset_query) // exclude text cards and queries without perms
.flatMap(dc => [dc.card].concat(dc.series || []))
const questions = cards
.filter(card => card.dataset_query) // exclude queries without perms
.map(card => new Question(card, metadata));
return dispatch(
......@@ -20,3 +30,26 @@ export const loadMetadataForDashboard = dashCards => (dispatch, getState) => {
),
);
};
const loadMetadataForLinkedTargets =
dashCards => async (dispatch, getState) => {
const linkTargets = dashCards.flatMap(card =>
getLinkTargets(card.visualization_settings),
);
const fetchRequests = linkTargets
.map(({ entity, entityId }) =>
entity.actions.fetch({ id: entityId }, { noEvent: true }),
)
.map(action => dispatch(action).catch(e => console.error(e)));
await Promise.all(fetchRequests);
const cards = linkTargets
.filter(({ entityType }) => entityType === "question")
.map(({ entityId }) =>
Questions.selectors.getObject(getState(), { entityId }),
)
.filter(card => card != null);
await dispatch(loadMetadataForCards(cards));
};
......@@ -7,7 +7,7 @@ import type { LocationDescriptor } from "history";
import { IconProps } from "metabase/components/Icon";
import Visualization from "metabase/visualizations/components/Visualization";
import WithVizSettingsData from "metabase/visualizations/hoc/WithVizSettingsData";
import WithVizSettingsData from "metabase/dashboard/hoc/WithVizSettingsData";
import { getVisualizationRaw } from "metabase/visualizations";
import QueryDownloadWidget from "metabase/query_builder/components/QueryDownloadWidget";
......
......@@ -5,38 +5,10 @@ import { withRouter } from "react-router";
import _ from "underscore";
import { getUserAttributes } from "metabase/selectors/user";
import Questions from "metabase/entities/questions";
import Dashboards from "metabase/entities/dashboards";
function hasLinkedQuestionOrDashboard({ type, linkType } = {}) {
if (type === "link") {
return linkType === "question" || linkType === "dashboard";
}
return false;
}
function mapLinkedEntityToEntityQuery({ linkType, targetId }) {
return {
entity: linkType === "question" ? Questions : Dashboards,
entityId: targetId,
};
}
import { getLinkTargets } from "metabase/lib/click-behavior";
// This HOC give access to data referenced in viz settings.
// We use it to fetch and select entities needed for dashboard drill actions (e.g. clicking through to a question)
const WithVizSettingsData = ComposedComponent => {
function getLinkTargets(settings) {
const { click_behavior, column_settings = {} } = settings || {};
return [
click_behavior,
...Object.values(column_settings).map(
settings => settings.click_behavior,
),
]
.filter(hasLinkedQuestionOrDashboard)
.map(mapLinkedEntityToEntityQuery);
}
return withRouter(
connect(
(state, props) => ({
......@@ -67,35 +39,6 @@ const WithVizSettingsData = ComposedComponent => {
dispatch => ({ dispatch }),
)(
class WithVizSettingsData extends React.Component {
dashcardSettings(props) {
const [firstSeries] = props.rawSeries || [{}];
const { visualization_settings } = firstSeries.card || {};
return visualization_settings;
}
fetch() {
getLinkTargets(this.dashcardSettings(this.props)).forEach(
({ entity, entityId }) =>
this.props.dispatch(
entity.actions.fetch({ id: entityId }, { noEvent: true }),
),
);
}
componentDidMount() {
this.fetch();
}
componentDidUpdate(prevProps) {
if (
!_.isEqual(
this.dashcardSettings(this.props),
this.dashcardSettings(prevProps),
)
) {
this.fetch();
}
}
render() {
return <ComposedComponent {..._.omit(this.props, "dispatch")} />;
}
......
import { msgid, ngettext, t } from "ttag";
import { getIn } from "icepick";
import Questions from "metabase/entities/questions";
import Dashboards from "metabase/entities/dashboards";
import Question from "metabase-lib/Question";
export function getClickBehaviorDescription(dashcard) {
......@@ -46,3 +48,28 @@ export function hasActionsMenu(dashcard) {
export function isTableDisplay(dashcard) {
return dashcard?.card?.display === "table";
}
export function getLinkTargets(settings) {
const { click_behavior, column_settings = {} } = settings || {};
return [
click_behavior,
...Object.values(column_settings).map(settings => settings.click_behavior),
]
.filter(hasLinkedQuestionOrDashboard)
.map(mapLinkedEntityToEntityQuery);
}
function hasLinkedQuestionOrDashboard({ type, linkType } = {}) {
if (type === "link") {
return linkType === "question" || linkType === "dashboard";
}
return false;
}
function mapLinkedEntityToEntityQuery({ linkType, targetId }) {
return {
entity: linkType === "question" ? Questions : Dashboards,
entityType: linkType,
entityId: targetId,
};
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment