return async (dispatch, getState) => { if (favorited) { await CardApi.favorite({ cardId }); } else { await CardApi.unfavorite({ cardId }); } MetabaseAnalytics.trackEvent("Questions", favorited ? "Favorite" : "Unfavorite"); return { id: cardId, favorite: favorited }; }
afterAll(async () => { PulseApi.form_input = normalFormInput await CardApi.delete({ cardId: questionCount.id() }) await CardApi.delete({ cardId: questionRaw.id() }) for (const pulse of await PulseApi.list()) { await PulseApi.delete({ pulseId: pulse.id }) } })
it("Should see a newly asked question in its questions list", async () => { var card = await CardApi.create(segmentCardDef) expect(card.name).toBe(segmentCardDef.name); await CardApi.delete({cardId: card.id}) const store = await createTestStore() store.pushPath("/reference/segments/"+segmentIds[0]+'/questions'); mount(store.connectContainer(<SegmentQuestionsContainer />)); await store.waitForActions([FETCH_SEGMENT_TABLE, LOAD_ENTITIES]) })
return async (dispatch, getState) => { if (cardId == null) { // bulk archive let selected = getSelectedEntities(getState()).filter(item => item.archived !== archived); selected.map(item => dispatch(setArchived(item.id, archived))); // TODO: errors if (undoable) { dispatch(addUndo(createUndo( archived ? "archived" : "unarchived", selected.map(item => setArchived(item.id, !archived)) ))); MetabaseAnalytics.trackEvent("Questions", archived ? "Bulk Archive" : "Bulk Unarchive", selected.length); } } else { let card = { ...getState().questions.entities.cards[cardId], archived: archived }; let response = await CardApi.update(card); if (undoable) { dispatch(addUndo(createUndo( archived ? "archived" : "unarchived", [setArchived(cardId, !archived)], !archived && card.collection ))); MetabaseAnalytics.trackEvent("Questions", archived ? "Archive" : "Unarchive"); } return response; } }
return async function(dispatch, getState) { let cards = await CardApi.list({ f: filterMode }); for (var c of cards) { c.updated_at = moment(c.updated_at); } return normalize(cards, [card]); };
return async (dispatch, getState) => { if (cardId == null) { // bulk label let selected = getSelectedEntities(getState()); selected.map(item => dispatch(setLabeled(item.id, labelId, labeled))); // TODO: errors if (undoable) { dispatch(addUndo(createUndo( labeled ? "labeled" : "unlabeled", selected.map(item => setLabeled(item.id, labelId, !labeled)) ))); MetabaseAnalytics.trackEvent("Questions", labeled ? "Bulk Apply Label" : "Bulk Remove Label", selected.length); } } else { const state = getState(); const labelSlug = getIn(state.questions, ["entities", "labels", labelId, "slug"]); const labels = getIn(state.questions, ["entities", "cards", cardId, "labels"]); const newLabels = labels.filter(id => id !== labelId); if (labeled) { newLabels.push(labelId); } if (labels.length !== newLabels.length) { await CardApi.updateLabels({ cardId, label_ids: newLabels }); if (undoable) { dispatch(addUndo(createUndo( labeled ? "labeled" : "unlabeled", [setLabeled(cardId, labelId, !labeled)] ))); MetabaseAnalytics.trackEvent("Questions", labeled ? "Apply Label" : "Remove Label"); } return { id: cardId, labels: newLabels, _changedLabelSlug: labelSlug, _changedLabeled: labeled }; } } }
it("Should see a newly asked question in its questions list", async () => { let card = await CardApi.create(metricCardDef); expect(card.name).toBe(metricCardDef.name); try { // see that there is a new question on the metric's questions page const store = await createTestStore(); store.pushPath("/reference/metrics/" + metricIds[0] + "/questions"); mount(store.connectContainer(<MetricQuestionsContainer />)); await store.waitForActions([FETCH_METRICS, FETCH_METRIC_TABLE]); } finally { // even if the code above results in an exception, try to delete the question await CardApi.delete({ cardId: card.id }); } });
return async (dispatch, getState) => { const state = getState(); if (cardId == null) { // bulk move let selected = getSelectedEntities(getState()); if (undoable) { dispatch(addUndo(createUndo( "moved", selected.map(item => setCollection(item.id, getCardCollectionId(state, item.id))) ))); MetabaseAnalytics.trackEvent("Questions", "Bulk Move to Collection"); } selected.map(item => dispatch(setCollection(item.id, collectionId))); } else { const collection = _.findWhere(state.collections.collections, { id: collectionId }); if (undoable) { dispatch(addUndo(createUndo( "moved", [setCollection(cardId, getCardCollectionId(state, cardId))] ))); MetabaseAnalytics.trackEvent("Questions", "Move to Collection"); } const card = await CardApi.update({ id: cardId, collection_id: collectionId }); return { ...card, _changedSectionSlug: collection && collection.slug } } }
export async function loadCard(cardId) { try { return await CardApi.get({ "cardId": cardId }); } catch (error) { console.log("error loading card", error); throw error; } }
return async (dispatch, getState) => { const state = getState(); const parameters = getParameters(state); // if we got a query directly on the action call then use it, otherwise take whatever is in our current state card = card || state.qb.card; parameterValues = parameterValues || state.qb.parameterValues || {}; const cardIsDirty = isCardDirty(card, state.qb.originalCard); card = { ...card, dataset_query: applyParameters(card, parameters, parameterValues) }; if (shouldUpdateUrl) { dispatch(updateUrl(card, { dirty: cardIsDirty })); } let cancelQueryDeferred = defer(); const startTime = new Date(); // make our api call function onQuerySuccess(queryResult) { dispatch(queryCompleted(card, queryResult)); } function onQueryError(error) { dispatch(queryErrored(startTime, error)); } // use the CardApi.query if the query is saved and not dirty so users with view but not create permissions can see it. if (card && card.id && !isDirty(state) && dirty !== true) { CardApi.query({ cardId: card.id, parameters: card.dataset_query.parameters }, { cancelled: cancelQueryDeferred.promise }).then(onQuerySuccess, onQueryError); } else { MetabaseApi.dataset(card.dataset_query, { cancelled: cancelQueryDeferred.promise }).then(onQuerySuccess, onQueryError); } MetabaseAnalytics.trackEvent("QueryBuilder", "Run Query", card.dataset_query.type); // HACK: prevent SQL editor from losing focus try { ace.edit("id_sql").focus() } catch (e) {} return cancelQueryDeferred; };
async (dispatch, getState) => { let card = { ...getState().qb.card, // grab the current card archived } let response = await CardApi.update(card) const type = archived ? "archived" : "unarchived" dispatch(addUndo(createUndo({ type, // eslint-disable-next-line react/display-name message: () => <div> { "Question was " + type + "."} </div>, action: archiveQuestion(card.id, !archived) }))); dispatch(push('/questions')) return response }
it("should be possible to view a embedded question", async () => { useSharedAdminLogin(); await CardApi.update({ id: question.id(), embedding_params: { id: "enabled", name: "enabled", source: "enabled", user_id: "enabled", }, enable_embedding: true, }); logout(); const token = jwt.sign( { resource: { question: question.id() }, params: {}, }, METABASE_SECRET_KEY, ); store = await createTestStore({ embedApp: true }); store.pushPath(Urls.embedCard(token) + "?id=1"); app = mount(store.getAppContainer()); await waitForRequestToComplete("GET", /\/card\/[^\/]+/); expect(app.find(".EmbedFrame-header .h4").text()).toEqual( "Test Question", ); // wait for the query to load await waitForRequestToComplete( "GET", /^\/api\/embed\/card\/[^\/]+\/query/, ); });
it("should be possible to view a public question", async () => { useSharedAdminLogin(); const publicQuestion = await CardApi.createPublicLink({ id: question.id(), }); logout(); store = await createTestStore({ publicApp: true }); store.pushPath(Urls.publicQuestion(publicQuestion.uuid) + "?id=1"); app = mount(store.getAppContainer()); await waitForRequestToComplete("GET", /^\/api\/[^\/]*\/card/); expect(app.find(".EmbedFrame-header .h4").text()).toEqual( "Test Question", ); // wait for the query to load await waitForRequestToComplete( "GET", /^\/api\/public\/card\/[^\/]+\/query/, ); });
return async function(dispatch, getState) { let cards = await CardApi.list({ f: filterMode }); return normalize(cards, [card]); };
export const createPublicLink = createAction(CREATE_PUBLIC_LINK, ({ id }) => CardApi.createPublicLink({ id }),
({ id }, embedding_params) => CardApi.update({ id, embedding_params }),
({ id }, enable_embedding) => CardApi.update({ id, enable_embedding }),
.map(async dc => CardApi.update(dc.card)));
export const updateEmbeddingParams = createAction(UPDATE_EMBEDDING_PARAMS, ({ id }, embedding_params) => CardApi.update({ id, embedding_params })
return async (dispatch, getState) => { let entityQuery = JSON.stringify(entityQueryObject); try { let result; dispatch(setRequestState({ statePath: ['questions', 'fetch'], state: "LOADING" })); if (entityType === "cards") { result = { entityType, entityQuery, ...normalize(momentifyArraysTimestamps(await CardApi.list(entityQueryObject)), [card]) }; } else if (entityType === "collections") { result = { entityType, entityQuery, ...normalize(momentifyArraysTimestamps(await CollectionsApi.list(entityQueryObject)), [collection]) }; } else { throw "Unknown entity type " + entityType; } dispatch(setRequestState({ statePath: ['questions', 'fetch'], state: "LOADED" })); return result; } catch (error) { throw { entityType, entityQuery, error }; } }
return async function(dispatch, getState) { await CardApi.delete({ cardId }); return cardId; };
return async function(dispatch, getState) { // If the dataset_query was filtered then we don't have permisison to view this card, so // shortcircuit and return a fake 403 if (!card.dataset_query) { return { dashcard_id: dashcard.id, card_id: card.id, result: { error: { status: 403 }} }; } const isPublicLink = Utils.isUUID(dashcard.dashboard_id); const { dashboardId, dashboards, parameterValues, dashcardData } = getState().dashboard; const dashboard = dashboards[dashboardId]; // if we have a parameter, apply it to the card query before we execute const datasetQuery = applyParameters(card, dashboard.parameters, parameterValues, dashcard && dashcard.parameter_mappings); if (!reload) { // if reload not set, check to see if the last result has the same query dict and return that const lastResult = getIn(dashcardData, [dashcard.id, card.id]); // "constraints" is added by the backend, remove it when comparing if (lastResult && Utils.equals(_.omit(lastResult.json_query, "constraints"), datasetQuery)) { return { dashcard_id: dashcard.id, card_id: card.id, result: lastResult }; } } if (clear) { // clears the card data to indicate the card is reloading dispatch(clearCardData(card.id, dashcard.id)); } let result = null; // start a timer that will fetch the expected card duration if the query takes too long let slowCardTimer = setTimeout(() => { if (result === null) { dispatch(fetchCardDuration(card, datasetQuery)); } }, DATASET_SLOW_TIMEOUT); // make the actual request if (isPublicLink) { result = await fetchDataOrError(PublicApi.dashboardCardQuery({ uuid: dashcard.dashboard_id, cardId: card.id, parameters: datasetQuery.parameters ? JSON.stringify(datasetQuery.parameters) : undefined })); } else { result = await fetchDataOrError(CardApi.query({cardId: card.id, parameters: datasetQuery.parameters})); } clearTimeout(slowCardTimer); return { dashcard_id: dashcard.id, card_id: card.id, result: result }; };
export const deletePublicLink = createAction(DELETE_PUBLIC_LINK, ({ id }) => CardApi.deletePublicLink({ id }),
export const updateEnableEmbedding = createAction(UPDATE_ENABLE_EMBEDDING, ({ id }, enable_embedding) => CardApi.update({ id, enable_embedding })
cleanup.fn(() => CardApi.update({ id: question.id(), archived: true, }),