Skip to content
Snippets Groups Projects
Unverified Commit 4f7b562c authored by Nemanja Glumac's avatar Nemanja Glumac Committed by GitHub
Browse files

Fix the stale collection information when the source in the data selector changes (#40063)


* Bridge out of sync collection information between the card and the state

* Account for models

* Use existing helpers

* Create virtual schema if it doesn't exist yet

* Add E2E reproduction for #39812

* Create virtual schema if it doesn't exist yet

* Create virtual schema if it doesn't exist yet

* Create virtual schema if it doesn't exist yet

* Expand the reproduction to include the nested question scenario

* Improve assertions

* Convert E2E repro to TS

* Fix tests

---------

Co-authored-by: default avatarAlexander Polyankin <alexander.polyankin@metabase.com>
parent 4a1455a5
No related branches found
No related tags found
No related merge requests found
import {
ORDERS_MODEL_ID,
ORDERS_COUNT_QUESTION_ID,
} from "e2e/support/cypress_sample_instance_data";
import {
restore,
visitModel,
openNotebook,
openQuestionActions,
popover,
visitQuestion,
} from "e2e/support/helpers";
describe("issue 39812", () => {
beforeEach(() => {
restore();
cy.signInAsAdmin();
});
it("moving the model to another collection should immediately be reflected in the data selector (metabase#39812-1)", () => {
visitModel(ORDERS_MODEL_ID);
openNotebook();
openDataSelector();
assertSourceCollection("Our analytics");
assertDataSource("Orders Model");
moveToCollection("First collection");
openDataSelector();
assertSourceCollection("First collection");
assertDataSource("Orders Model");
});
it("moving the source question should immediately reflect in the data selector for the nested question that depends on it (metabase#39812-2)", () => {
const SOURCE_QUESTION_ID = ORDERS_COUNT_QUESTION_ID;
// Rename the source question to make assertions more explicit
const sourceQuestionName = "Source Question";
cy.request("PUT", `/api/card/${ORDERS_COUNT_QUESTION_ID}`, {
name: sourceQuestionName,
});
const nestedQuestionDetails = {
name: "Nested Question",
query: { "source-table": `card__${SOURCE_QUESTION_ID}` },
};
cy.createQuestion(nestedQuestionDetails, {
wrapId: true,
idAlias: "nestedQuestionId",
});
visitQuestion("@nestedQuestionId");
openNotebook();
openDataSelector();
assertSourceCollection("Our analytics");
assertDataSource(sourceQuestionName);
cy.log("Move the source question to another collection");
visitQuestion(SOURCE_QUESTION_ID);
openNotebook();
moveToCollection("First collection");
cy.log("Make sure the source change is reflected in a nested question");
visitQuestion("@nestedQuestionId");
openNotebook();
openDataSelector();
assertSourceCollection("First collection");
assertDataSource(sourceQuestionName);
});
});
function moveToCollection(collection: string) {
openQuestionActions();
popover().findByTextEnsureVisible("Move").click();
cy.findByRole("dialog").within(() => {
cy.intercept("GET", "/api/collection/tree**").as("updateCollectionTree");
cy.findAllByTestId("item-picker-item")
.filter(`:contains(${collection})`)
.click();
cy.button("Move").click();
cy.wait("@updateCollectionTree");
});
}
function openDataSelector() {
cy.findByTestId("data-step-cell").click();
}
function assertItemSelected(item: string) {
cy.findByLabelText(item).should("have.attr", "aria-selected", "true");
}
function assertSourceCollection(collection: string) {
return assertItemSelected(collection);
}
function assertDataSource(questionOrModel: string) {
return assertItemSelected(questionOrModel);
}
import { updateIn } from "icepick";
import { assocIn, updateIn } from "icepick";
import Questions from "metabase/entities/questions";
import { createEntity } from "metabase/lib/entities";
......@@ -7,7 +7,9 @@ import { getMetadata } from "metabase/selectors/metadata";
import { MetabaseApi } from "metabase/services";
import {
getCollectionVirtualSchemaId,
getCollectionVirtualSchemaName,
getQuestionVirtualTableId,
SAVED_QUESTIONS_VIRTUAL_DB_ID,
} from "metabase-lib/v1/metadata/utils/saved-questions";
import {
generateSchemaId,
......@@ -84,16 +86,16 @@ export default createEntity({
}
if (type === Questions.actionTypes.UPDATE && !error) {
const { question } = payload;
const schemaId = getCollectionVirtualSchemaId(question.collection, {
isDatasets: question.type === "model",
const { question: card } = payload;
const virtualSchemaId = getCollectionVirtualSchemaId(card.collection, {
isDatasets: card.type === "model",
});
const virtualQuestionId = getQuestionVirtualTableId(question.id);
const virtualSchemaName = getCollectionVirtualSchemaName(card.collection);
const virtualQuestionId = getQuestionVirtualTableId(card.id);
const previousSchemaContainingTheQuestion =
getPreviousSchemaContainingTheQuestion(
state,
schemaId,
virtualSchemaId,
virtualQuestionId,
);
......@@ -105,16 +107,20 @@ export default createEntity({
);
}
if (!state[schemaId]) {
return state;
if (!state[virtualSchemaId]) {
state = assocIn(state, [virtualSchemaId], {
id: virtualSchemaId,
name: virtualSchemaName,
database: SAVED_QUESTIONS_VIRTUAL_DB_ID,
});
}
return updateIn(state, [schemaId, "tables"], tables => {
return updateIn(state, [virtualSchemaId, "tables"], tables => {
if (!tables) {
return tables;
}
if (question.archived) {
if (card.archived) {
return tables.filter(id => id !== virtualQuestionId);
}
return addTableAvoidingDuplicates(tables, virtualQuestionId);
......
......@@ -3,7 +3,10 @@ import fetchMock from "fetch-mock";
import { getStore } from "__support__/entities-store";
import Questions from "metabase/entities/questions";
import Schemas from "metabase/entities/schemas";
import { ROOT_COLLECTION_VIRTUAL_SCHEMA } from "metabase-lib/v1/metadata/utils/saved-questions";
import {
ROOT_COLLECTION_VIRTUAL_SCHEMA,
SAVED_QUESTIONS_VIRTUAL_DB_ID,
} from "metabase-lib/v1/metadata/utils/saved-questions";
describe("schema entity", () => {
let store;
......@@ -251,7 +254,7 @@ describe("schema entity", () => {
});
});
it("should not add question ID when it is unarchived if collection schema is not present in store", () => {
it("should add question ID when it is unarchived if collection schema is not present in store", () => {
const nextState = Schemas.reducer(
{
[ROOT_COLLECTION_VIRTUAL_SCHEMA]: {
......@@ -265,6 +268,11 @@ describe("schema entity", () => {
[ROOT_COLLECTION_VIRTUAL_SCHEMA]: {
tables: ["card__123"],
},
[`${SAVED_QUESTIONS_VIRTUAL_DB_ID}:foo`]: {
id: `${SAVED_QUESTIONS_VIRTUAL_DB_ID}:foo`,
name: "foo",
database: SAVED_QUESTIONS_VIRTUAL_DB_ID,
},
});
});
});
......
......@@ -27,6 +27,8 @@ import { MetabaseApi } from "metabase/services";
import {
convertSavedQuestionToVirtualTable,
getQuestionVirtualTableId,
getCollectionVirtualSchemaId,
getCollectionVirtualSchemaName,
} from "metabase-lib/v1/metadata/utils/saved-questions";
const listTablesForDatabase = async (...args) =>
......@@ -169,25 +171,36 @@ const Tables = createEntity({
if (type === Questions.actionTypes.UPDATE && !error) {
const card = payload.question;
const virtualQuestionId = getQuestionVirtualTableId(card.id);
const virtualTableId = getQuestionVirtualTableId(card.id);
if (card.archived && state[virtualQuestionId]) {
delete state[virtualQuestionId];
if (card.archived && state[virtualTableId]) {
delete state[virtualTableId];
return state;
}
if (state[virtualQuestionId]) {
const virtualQuestion = state[virtualQuestionId];
if (state[virtualTableId]) {
const virtualTable = state[virtualTableId];
const virtualSchemaId = getCollectionVirtualSchemaId(card.collection, {
isDatasets: card.type === "model",
});
const virtualSchemaName = getCollectionVirtualSchemaName(
card.collection,
);
if (
virtualQuestion.display_name !== card.name ||
virtualQuestion.moderated_status !== card.moderated_status ||
virtualQuestion.description !== card.description
virtualTable.display_name !== card.name ||
virtualTable.moderated_status !== card.moderated_status ||
virtualTable.description !== card.description ||
virtualTable.schema !== virtualSchemaId ||
virtualTable.schema_name !== virtualSchemaName
) {
state = updateIn(state, [virtualQuestionId], table => ({
state = updateIn(state, [virtualTableId], table => ({
...table,
display_name: card.name,
moderated_status: card.moderated_status,
description: card.description,
schema: virtualSchemaId,
schema_name: virtualSchemaName,
}));
}
......@@ -196,7 +209,7 @@ const Tables = createEntity({
return {
...state,
[virtualQuestionId]: convertSavedQuestionToVirtualTable(card),
[virtualTableId]: convertSavedQuestionToVirtualTable(card),
};
}
......
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