diff --git a/frontend/src/metabase-types/api/mocks/user.ts b/frontend/src/metabase-types/api/mocks/user.ts index a92fc89907aea146075691539d5cc79d0a8a9710..674a681f7a970b9335d29efdcb4b65e6397f2084 100644 --- a/frontend/src/metabase-types/api/mocks/user.ts +++ b/frontend/src/metabase-types/api/mocks/user.ts @@ -8,6 +8,7 @@ export const createMockUser = (opts?: Partial<User>): User => ({ email: "user@metabase.test", locale: null, google_auth: false, + login_attributes: null, is_active: true, is_qbnewb: false, is_superuser: false, diff --git a/frontend/src/metabase-types/api/user.ts b/frontend/src/metabase-types/api/user.ts index 6f216cd6ca491522f510d78207164152804941e9..5365546e938512f32b3d9a7a8b6f6d7f3d9f73d2 100644 --- a/frontend/src/metabase-types/api/user.ts +++ b/frontend/src/metabase-types/api/user.ts @@ -19,6 +19,7 @@ export interface BaseUser { export interface User extends BaseUser { google_auth: boolean; + login_attributes: string[] | null; is_installer: boolean; has_invited_second_user: boolean; has_question_and_dashboard: boolean; diff --git a/frontend/src/metabase-types/store/state.ts b/frontend/src/metabase-types/store/state.ts index 07408871414cf034eaa2b9de1ad9a8bfc1cbd381..c0d0ee77e441358e16fc24138560d2f3c2cb7b38 100644 --- a/frontend/src/metabase-types/store/state.ts +++ b/frontend/src/metabase-types/store/state.ts @@ -11,7 +11,7 @@ import { SetupState } from "./setup"; export interface State { admin: AdminState; app: AppState; - currentUser: User; + currentUser: User | null; embed: EmbedState; entities: EntitiesState; form: FormState; diff --git a/frontend/src/metabase/account/password/containers/UserPasswordApp/UserPasswordApp.tsx b/frontend/src/metabase/account/password/containers/UserPasswordApp/UserPasswordApp.tsx index 4e5f026b81597ddae73fb2ce5f199e8bfd02f2b3..842a3b5c7e823376809b36c1215f25081a061416 100644 --- a/frontend/src/metabase/account/password/containers/UserPasswordApp/UserPasswordApp.tsx +++ b/frontend/src/metabase/account/password/containers/UserPasswordApp/UserPasswordApp.tsx @@ -1,11 +1,16 @@ import { connect } from "react-redux"; + +import { checkNotNull } from "metabase/core/utils/types"; + import { getUser } from "metabase/selectors/user"; -import { State } from "metabase-types/store"; + +import type { State } from "metabase-types/store"; + import { updatePassword, validatePassword } from "../../actions"; import UserPasswordForm from "../../components/UserPasswordForm"; const mapStateToProps = (state: State) => ({ - user: getUser(state), + user: checkNotNull(getUser(state)), onValidatePassword: validatePassword, onSubmit: updatePassword, }); diff --git a/frontend/src/metabase/account/profile/containers/UserProfileApp/UserProfileApp.tsx b/frontend/src/metabase/account/profile/containers/UserProfileApp/UserProfileApp.tsx index dbde8a6ed2619515230c607717aac85b67d4902e..b060b1eadf5941927e027b2d2d9718e9c5472a86 100644 --- a/frontend/src/metabase/account/profile/containers/UserProfileApp/UserProfileApp.tsx +++ b/frontend/src/metabase/account/profile/containers/UserProfileApp/UserProfileApp.tsx @@ -1,12 +1,17 @@ import { connect } from "react-redux"; + +import { checkNotNull } from "metabase/core/utils/types"; + import { getUser } from "metabase/selectors/user"; -import { State } from "metabase-types/store"; + +import type { State } from "metabase-types/store"; + import UserProfileForm from "../../components/UserProfileForm"; import { updateUser } from "../../actions"; import { getIsSsoUser, getLocales } from "../../selectors"; const mapStateToProps = (state: State) => ({ - user: getUser(state), + user: checkNotNull(getUser(state)), locales: getLocales(state), isSsoUser: getIsSsoUser(state), }); diff --git a/frontend/src/metabase/account/profile/selectors.ts b/frontend/src/metabase/account/profile/selectors.ts index 46c12d573cc59c507f107bec8ceb202d59043438..bff59f25a30d2629f6f3dfc543dd213f9d3e34c9 100644 --- a/frontend/src/metabase/account/profile/selectors.ts +++ b/frontend/src/metabase/account/profile/selectors.ts @@ -1,9 +1,14 @@ import { createSelector } from "reselect"; + import { getUser } from "metabase/selectors/user"; -import { PLUGIN_IS_PASSWORD_USER } from "metabase/plugins"; import { getSettings } from "metabase/selectors/settings"; -export const getIsSsoUser = createSelector([getUser], user => { +import { PLUGIN_IS_PASSWORD_USER } from "metabase/plugins"; + +export const getIsSsoUser = createSelector(getUser, user => { + if (!user) { + return false; + } return !PLUGIN_IS_PASSWORD_USER.every(predicate => predicate(user)); }); diff --git a/frontend/src/metabase/home/homepage/containers/HomeGreeting/HomeGreeting.tsx b/frontend/src/metabase/home/homepage/containers/HomeGreeting/HomeGreeting.tsx index e56cb1f9d2f3a23f565c51435356aa23b1e770b1..ad222fddaea9378f5f9cffacaceeef7ad6dd81dd 100644 --- a/frontend/src/metabase/home/homepage/containers/HomeGreeting/HomeGreeting.tsx +++ b/frontend/src/metabase/home/homepage/containers/HomeGreeting/HomeGreeting.tsx @@ -1,10 +1,15 @@ import { connect } from "react-redux"; + +import { checkNotNull } from "metabase/core/utils/types"; + import { getUser } from "metabase/selectors/user"; -import { State } from "metabase-types/store"; + +import type { State } from "metabase-types/store"; + import HomeGreeting from "../../components/HomeGreeting"; const mapStateToProps = (state: State) => ({ - user: getUser(state), + user: checkNotNull(getUser(state)), showLogo: state.settings.values["show-metabot"], }); diff --git a/frontend/src/metabase/plugins/index.ts b/frontend/src/metabase/plugins/index.ts index 6ae32f1fdd1170fa46480b3fc07274e94dec757c..8e5ab1964e3df77ff490c08cd4d4b3f51421e517 100644 --- a/frontend/src/metabase/plugins/index.ts +++ b/frontend/src/metabase/plugins/index.ts @@ -1,20 +1,24 @@ -import { t } from "ttag"; import React from "react"; +import { t } from "ttag"; + import PluginPlaceholder from "metabase/plugins/components/PluginPlaceholder"; -import { + +import type { DatabaseEntityId, PermissionSubject, } from "metabase/admin/permissions/types"; -import { - Collection, + +import type { Bookmark, - GroupsPermissions, + Collection, Dataset, Group, + GroupsPermissions, + User, } from "metabase-types/api"; -import { AdminPathKey, State } from "metabase-types/store"; -import { User } from "metabase-types/types/User"; -import Question from "metabase-lib/Question"; +import type { AdminPathKey, State } from "metabase-types/store"; +import type Question from "metabase-lib/Question"; + import { PluginGroupManagersType } from "./types"; // Plugin integration points. All exports must be objects or arrays so they can be mutated by plugins. diff --git a/frontend/src/metabase/query_builder/actions/core/initializeQB.ts b/frontend/src/metabase/query_builder/actions/core/initializeQB.ts index b6e08805b0d6a39ad43f1733b80b10daab1dfcae..41c7271d2c8f280f7d9385fec9535a4d9ba4bd26 100644 --- a/frontend/src/metabase/query_builder/actions/core/initializeQB.ts +++ b/frontend/src/metabase/query_builder/actions/core/initializeQB.ts @@ -308,7 +308,7 @@ async function handleQBInit( question = question.lockDisplay(); const currentUser = getUser(getState()); - if (currentUser.is_qbnewb) { + if (currentUser?.is_qbnewb) { uiControls.isShowingNewbModal = true; MetabaseAnalytics.trackStructEvent("QueryBuilder", "Show Newb Modal"); } diff --git a/frontend/src/metabase/selectors/user.js b/frontend/src/metabase/selectors/user.ts similarity index 73% rename from frontend/src/metabase/selectors/user.js rename to frontend/src/metabase/selectors/user.ts index f981584fbd96d69151e9ee9466423f8c9cbe1745..0f46fdf65521df2e550ba5a6aaa077ed681a0df7 100644 --- a/frontend/src/metabase/selectors/user.js +++ b/frontend/src/metabase/selectors/user.ts @@ -1,13 +1,15 @@ import { createSelector } from "reselect"; import { PLUGIN_APPLICATION_PERMISSIONS } from "metabase/plugins"; -export const getUser = state => state.currentUser; +import type { State } from "metabase-types/store"; + +export const getUser = (state: State) => state.currentUser; export const getUserId = createSelector([getUser], user => user?.id); export const getUserIsAdmin = createSelector( [getUser], - user => (user && user.is_superuser) || false, + user => user?.is_superuser || false, ); export const canManageSubscriptions = createSelector( @@ -21,10 +23,10 @@ export const canManageSubscriptions = createSelector( export const getUserAttributes = createSelector( [getUser], - user => (user && user.login_attributes) || [], + user => user?.login_attributes || [], ); export const getUserPersonalCollectionId = createSelector( [getUser], - user => (user && user.personal_collection_id) || null, + user => user?.personal_collection_id, ); diff --git a/frontend/src/metabase/selectors/user.unit.spec.js b/frontend/src/metabase/selectors/user.unit.spec.js deleted file mode 100644 index 9bde3e13bdb59e5efba9918481d651deb730c00b..0000000000000000000000000000000000000000 --- a/frontend/src/metabase/selectors/user.unit.spec.js +++ /dev/null @@ -1,23 +0,0 @@ -import { getUserIsAdmin } from "./user"; - -describe("metabase/selectors/user", () => { - it("should return true if user is an admin", () => { - const state = { - currentUser: { - is_superuser: true, - }, - }; - - expect(getUserIsAdmin(state)).toBe(true); - }); - - it("should return false if user is not an admin", () => { - const state = { - currentUser: { - is_superuser: false, - }, - }; - - expect(getUserIsAdmin(state)).toBe(false); - }); -}); diff --git a/frontend/src/metabase/selectors/user.unit.spec.ts b/frontend/src/metabase/selectors/user.unit.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..07fb28a1a3d79e695f240fc582a7d584dca8648c --- /dev/null +++ b/frontend/src/metabase/selectors/user.unit.spec.ts @@ -0,0 +1,21 @@ +import { createMockUser } from "metabase-types/api/mocks"; +import { createMockState } from "metabase-types/store/mocks"; +import { getUserIsAdmin } from "./user"; + +describe("metabase/selectors/user", () => { + it("should return true if user is an admin", () => { + const state = createMockState({ + currentUser: createMockUser({ is_superuser: true }), + }); + + expect(getUserIsAdmin(state)).toBe(true); + }); + + it("should return false if user is not an admin", () => { + const state = createMockState({ + currentUser: createMockUser({ is_superuser: false }), + }); + + expect(getUserIsAdmin(state)).toBe(false); + }); +});