Compare commits

...

1 Commits

Author SHA1 Message Date
Oisin Coveney
b83d5b9113 Convert saveToCollection to targetCollection 2025-03-11 14:45:46 +02:00
18 changed files with 51 additions and 42 deletions

View File

@@ -48,6 +48,6 @@ export default function App() {
With static questions, you set a specific collection as the collection people can save items to, so that they don't have bother picking a collection. To hard-code a collection:
1. Set `isSaveEnabled` to true.
2. Set `saveToCollection` to the collection ID you want people to save items to.
2. Set `targetCollection` to the collection ID you want people to save items to.
For more options, see [Question props](./questions.md#question-props).

View File

@@ -73,7 +73,7 @@ export default function App() {
| `onSave` | `() => void` | (optional) A callback function that triggers when a user saves the question. Only relevant when `isSaveEnabled = true` |
| `plugins` | `{ mapQuestionClickActions: Function }` or null | Additional mapper function to override or add drill-down menu |
| `questionId` | number or string | (required) The ID of the question. This is either:<br>- The numerical ID when accessing a question link, e.g., `http://localhost:3000/question/1-my-question` where the ID is `1`<br>- The `entity_id` key of the question object. You can find a question's Entity ID in the info panel when viewing a question<br>- `new` to show the notebook editor for creating new questions. `isSaveEnabled` must be `true` to allow saving the question |
| `saveToCollection` | number \| "root" \| "personal" | (optional) The target collection to save the question to. This will hide the collection picker from the save modal. Only applicable to interactive questions |
| `targetCollection` | number \| "root" \| "personal" | (optional) The collection to save the question to. This will hide the collection picker from the save modal. Only applicable to interactive questions |
| `title` | boolean \| string \| `ReactNode` \| `() => ReactNode` | (optional) Determines whether the question title is displayed, and allows a custom title to be displayed instead of the default question title. Shown by default. Only applicable to interactive questions when using the default layout |
| `withChartTypeSelector` | boolean | (optional, default: `true`) Determines whether the chart type selector and corresponding settings button are shown. Only relevant when using the default layout |
| `withResetButton` | boolean | (optional, default: `true`) Determines whether a reset button is displayed. Only relevant when using the default layout |

View File

@@ -155,7 +155,7 @@ describe("scenarios > embedding-sdk > interactive-question", () => {
it("can save a question to a pre-defined collection", () => {
mountInteractiveQuestion({
saveToCollection: Number(THIRD_COLLECTION_ID),
targetCollection: Number(THIRD_COLLECTION_ID),
});
saveInteractiveQuestionAsNewQuestion({
@@ -174,7 +174,7 @@ describe("scenarios > embedding-sdk > interactive-question", () => {
cy.intercept("/api/user/current").as("getUser");
mountInteractiveQuestion({
saveToCollection: "personal",
targetCollection: "personal",
});
cy.wait("@getUser").then(({ response: userResponse }) => {

View File

@@ -12,7 +12,7 @@ export type SdkSaveQuestionFormProps = {
};
export const SdkSaveQuestionForm = ({ onCancel }: SdkSaveQuestionFormProps) => {
const { question, originalQuestion, onSave, onCreate, saveToCollection } =
const { question, originalQuestion, onSave, onCreate, targetCollection } =
useInteractiveQuestionContext();
if (!question) {
@@ -26,7 +26,7 @@ export const SdkSaveQuestionForm = ({ onCancel }: SdkSaveQuestionFormProps) => {
onCreate={onCreate}
onSave={onSave}
multiStep={false}
saveToCollection={saveToCollection}
targetCollection={targetCollection}
>
<Stack p="md">
<Title>

View File

@@ -53,7 +53,7 @@ export const InteractiveQuestionProvider = ({
onSave,
isSaveEnabled = true,
entityTypeFilter,
saveToCollection,
targetCollection,
initialSqlParameters,
}: InteractiveQuestionProviderProps) => {
const {
@@ -157,7 +157,7 @@ export const InteractiveQuestionProvider = ({
onCreate: handleCreate,
modelsFilterList: mapEntityTypeFilterToDataPickerModels(entityTypeFilter),
isSaveEnabled,
saveToCollection,
targetCollection,
isCardIdError,
};

View File

@@ -32,7 +32,7 @@ type InteractiveQuestionConfig = {
/** Initial values for the SQL parameters */
initialSqlParameters?: ParameterValues;
} & Pick<SaveQuestionProps, "saveToCollection">;
} & Pick<SaveQuestionProps, "targetCollection">;
export type QuestionMockLocationParameters = {
location: { search: string; hash: string; pathname: string };
@@ -59,7 +59,7 @@ export type InteractiveQuestionContextType = Omit<
> &
Pick<
InteractiveQuestionConfig,
"onNavigateBack" | "isSaveEnabled" | "saveToCollection"
"onNavigateBack" | "isSaveEnabled" | "targetCollection"
> &
Pick<QBNotebookProps, "modelsFilterList"> & {
plugins: InteractiveQuestionConfig["componentPlugins"] | null;

View File

@@ -54,7 +54,7 @@ export const InteractiveQuestionResult = ({
onCreate,
onSave,
isSaveEnabled,
saveToCollection,
targetCollection,
isCardIdError,
} = useInteractiveQuestionContext();
@@ -174,7 +174,7 @@ export const InteractiveQuestionResult = ({
await onSave(question);
closeSaveModal();
}}
saveToCollection={saveToCollection}
targetCollection={targetCollection}
/>
)}
</FlexibleSizeComponent>

View File

@@ -143,7 +143,7 @@ export const QuestionEditor = ({
onSave,
plugins,
entityTypeFilter,
saveToCollection,
targetCollection,
}: InteractiveQuestionProps) => (
<InteractiveQuestion
questionId={questionId}
@@ -152,7 +152,7 @@ export const QuestionEditor = ({
onBeforeSave={onBeforeSave}
isSaveEnabled={isSaveEnabled}
entityTypeFilter={entityTypeFilter}
saveToCollection={saveToCollection}
targetCollection={targetCollection}
>
<QuestionEditorInner />
</InteractiveQuestion>

View File

@@ -52,7 +52,7 @@ export const Default = {
args: {
questionId: QUESTION_ID,
isSaveEnabled: true,
saveToCollection: undefined,
targetCollection: undefined,
title: true,
withResetButton: true,
},
@@ -72,7 +72,7 @@ export const EditorOnly = {
args: {
questionId: QUESTION_ID,
isSaveEnabled: true,
saveToCollection: undefined,
targetCollection: undefined,
},
};

View File

@@ -39,7 +39,7 @@ export type InteractiveQuestionProps = PropsWithChildren<{
* When this is defined, the collection picker will be hidden and
* the question will be saved to this collection.
**/
saveToCollection?: SDKCollectionReference;
targetCollection?: SDKCollectionReference;
}> &
Pick<
InteractiveQuestionProviderProps,
@@ -64,7 +64,7 @@ export const _InteractiveQuestion = ({
onSave,
entityTypeFilter,
isSaveEnabled,
saveToCollection,
targetCollection,
withChartTypeSelector = true,
initialSqlParameters,
}: InteractiveQuestionProps &
@@ -77,7 +77,7 @@ export const _InteractiveQuestion = ({
onSave={onSave}
entityTypeFilter={entityTypeFilter}
isSaveEnabled={isSaveEnabled}
saveToCollection={saveToCollection}
targetCollection={targetCollection}
initialSqlParameters={initialSqlParameters}
>
{children ?? (

View File

@@ -10,7 +10,7 @@ export const ModifyQuestion = ({
onBeforeSave,
entityTypeFilter,
isSaveEnabled,
saveToCollection,
targetCollection,
}: InteractiveQuestionProps) => (
<QuestionEditor
questionId={questionId}
@@ -19,6 +19,6 @@ export const ModifyQuestion = ({
onBeforeSave={onBeforeSave}
entityTypeFilter={entityTypeFilter}
isSaveEnabled={isSaveEnabled}
saveToCollection={saveToCollection}
targetCollection={targetCollection}
/>
);

View File

@@ -35,6 +35,6 @@ export const Default = {
args: {
questionId: QUESTION_ID,
isSaveEnabled: true,
saveToCollection: undefined,
targetCollection: undefined,
},
};

View File

@@ -38,7 +38,7 @@ export const SaveQuestionForm = ({
originalQuestion,
showSaveType,
values,
saveToCollection,
targetCollection,
saveToDashboard,
} = useSaveQuestionContext();
@@ -58,7 +58,7 @@ export const SaveQuestionForm = ({
: ["collection"];
const showPickerInput =
values.saveType === "create" && !saveToCollection && !saveToDashboard;
values.saveType === "create" && !targetCollection && !saveToDashboard;
return (
<Form>

View File

@@ -31,7 +31,7 @@ type SaveQuestionContextType = {
setValues: (values: FormValues) => void;
showSaveType: boolean;
multiStep: boolean;
saveToCollection?: CollectionId;
targetCollection?: CollectionId;
saveToDashboard?: DashboardId;
};
@@ -61,7 +61,7 @@ export const SaveQuestionProvider = ({
onCreate,
onSave,
multiStep = false,
saveToCollection,
targetCollection: userTargetCollection,
children,
}: PropsWithChildren<SaveQuestionProps>) => {
const [originalQuestion] = useState(latestOriginalQuestion); // originalQuestion from props changes during saving
@@ -72,9 +72,9 @@ export const SaveQuestionProvider = ({
const currentUser = useSelector(getCurrentUser);
const targetCollection =
currentUser && saveToCollection === "personal"
currentUser && userTargetCollection === "personal"
? currentUser.personal_collection_id
: saveToCollection;
: userTargetCollection;
const [hasLoadedRecentItems, setHasLoadedRecentItems] = useState(false);
const { data: recentItems, isLoading } = useListRecentsQuery(
@@ -143,7 +143,7 @@ export const SaveQuestionProvider = ({
question,
onSave,
onCreate,
saveToCollection: targetCollection,
targetCollection: targetCollection,
}),
[originalQuestion, question, onSave, onCreate, targetCollection],
);
@@ -183,7 +183,7 @@ export const SaveQuestionProvider = ({
setValues,
showSaveType,
multiStep,
saveToCollection,
targetCollection,
saveToDashboard,
}}
>

View File

@@ -28,7 +28,7 @@ export type SaveQuestionProps = {
* When this is defined, the collection picker will be hidden and
* the question will be saved to this collection.
**/
saveToCollection?: CollectionId;
targetCollection?: CollectionId;
};
export type FormValues = {
@@ -58,7 +58,7 @@ export type CreateQuestionOptions = {
dashboardTabId?: DashboardTabId | undefined;
},
) => Promise<Question>;
} & Pick<SaveQuestionProps, "saveToCollection">;
} & Pick<SaveQuestionProps, "targetCollection">;
export type SubmitQuestionOptions = CreateQuestionOptions & {
originalQuestion: Question | null;

View File

@@ -29,18 +29,18 @@ const updateQuestion = async (options: UpdateQuestionOptions) => {
};
export const createQuestion = async (options: CreateQuestionOptions) => {
const { details, question, onCreate, saveToCollection } = options;
const { details, question, onCreate, targetCollection } = options;
if (details.saveType !== "create") {
return;
}
// `saveToCollection` is used to override the target collection of the question,
// `targetCollection` is used to override the target collection of the question,
// this is mainly used for the embedding SDK.
const collectionId = canonicalCollectionId(
isNullOrUndefined(saveToCollection)
isNullOrUndefined(targetCollection)
? details.collection_id
: saveToCollection,
: targetCollection,
);
const dashboardId = details.dashboard_id;
const dashboardTabId = details.dashboard_tab_id
@@ -66,7 +66,7 @@ export async function submitQuestion(options: SubmitQuestionOptions) {
question,
onSave,
onCreate,
saveToCollection,
targetCollection,
} = options;
if (details.saveType === "overwrite" && originalQuestion) {
@@ -80,7 +80,7 @@ export async function submitQuestion(options: SubmitQuestionOptions) {
question,
details,
onCreate,
saveToCollection,
targetCollection,
});
}
}

View File

@@ -18,7 +18,7 @@ export const SaveQuestionModal = ({
question,
closeOnSuccess,
initialCollectionId,
saveToCollection,
targetCollection,
...modalProps
}: SaveQuestionModalProps) => {
return (
@@ -37,7 +37,7 @@ export const SaveQuestionModal = ({
onSave={onSave}
multiStep={multiStep}
initialCollectionId={initialCollectionId}
saveToCollection={saveToCollection}
targetCollection={targetCollection}
>
<Modal.Root padding="2.5rem" {...modalProps}>
<Modal.Overlay />

View File

@@ -3,10 +3,19 @@ import _ from "underscore";
import { skipToken } from "metabase/api";
import { useTranslateEntityIdQuery } from "metabase/api/entity-id";
import type { BaseEntityId, CardId, DashboardId } from "metabase-types/api";
import type {
BaseEntityId,
CardId,
CollectionId,
DashboardId,
} from "metabase-types/api";
import { isBaseEntityID } from "metabase-types/api/entity-id";
type SUPPORTED_ENTITIES = { dashboard: DashboardId; card: CardId };
type SUPPORTED_ENTITIES = {
dashboard: DashboardId;
card: CardId;
collection: CollectionId;
};
/**
* A hook that validates and potentially translates an entity ID.