diff --git a/src/dispatch/auth/models.py b/src/dispatch/auth/models.py index d6cd1828d2d8..56827f9ca588 100644 --- a/src/dispatch/auth/models.py +++ b/src/dispatch/auth/models.py @@ -54,6 +54,7 @@ class DispatchUser(Base, TimeStampMixin): email = Column(String, unique=True) password = Column(LargeBinary, nullable=False) last_mfa_time = Column(DateTime, nullable=True) + experimental_features = Column(Boolean, default=False) # relationships events = relationship("Event", backref="dispatch_user") @@ -157,6 +158,7 @@ class UserLoginResponse(DispatchBase): class UserRead(UserBase): id: PrimaryKey role: Optional[str] = Field(None, nullable=True) + experimental_features: Optional[bool] class UserUpdate(DispatchBase): @@ -164,6 +166,7 @@ class UserUpdate(DispatchBase): password: Optional[str] = Field(None, nullable=True) projects: Optional[List[UserProject]] organizations: Optional[List[UserOrganization]] + experimental_features: Optional[bool] role: Optional[str] = Field(None, nullable=True) @validator("password", pre=True) diff --git a/src/dispatch/auth/service.py b/src/dispatch/auth/service.py index 29f8ca5be8ee..8d2b57ef1385 100644 --- a/src/dispatch/auth/service.py +++ b/src/dispatch/auth/service.py @@ -245,6 +245,9 @@ def update(*, db_session, user: DispatchUser, user_in: UserUpdate) -> DispatchUs ) ) + if experimental_features := user_in.experimental_features: + user.experimental_features = experimental_features + db_session.commit() return user diff --git a/src/dispatch/database/revisions/core/versions/2023-09-27_5c60513d6e5e.py b/src/dispatch/database/revisions/core/versions/2023-09-27_5c60513d6e5e.py new file mode 100644 index 000000000000..c124e41afa3c --- /dev/null +++ b/src/dispatch/database/revisions/core/versions/2023-09-27_5c60513d6e5e.py @@ -0,0 +1,28 @@ +"""Adds last_mfa_time to DispatchUser + +Revision ID: 5c60513d6e5e +Revises: 3dd4d12844dc +Create Date: 2023-09-27 15:17:00.450716 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "5c60513d6e5e" +down_revision = "3dd4d12844dc" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("dispatch_user", sa.Column("experimental_features", sa.Boolean(), default=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("dispatch_user", "experimental_features") + # ### end Alembic commands ### diff --git a/src/dispatch/static/dispatch/src/auth/store.js b/src/dispatch/static/dispatch/src/auth/store.js index 1bf94015d960..b8cafad588ee 100644 --- a/src/dispatch/static/dispatch/src/auth/store.js +++ b/src/dispatch/static/dispatch/src/auth/store.js @@ -25,6 +25,7 @@ const state = { email: "", projects: [], role: null, + experimental_features: false, }, selected: { ...getDefaultSelectedState(), @@ -148,6 +149,15 @@ const actions = { commit("SET_USER_LOGOUT") router.go() }, + getExperimentalFeatures({ commit }) { + UserApi.getUserInfo() + .then((response) => { + commit("SET_EXPERIMENTAL_FEATURES", response.data.experimental_features) + }) + .catch((error) => { + console.error("Error occurred while updating experimental features: ", error) + }) + }, createExpirationCheck({ state, commit }) { // expiration time minus 10 min let expire_at = subMinutes(fromUnixTime(state.currentUser.exp), 10) @@ -195,6 +205,9 @@ const mutations = { } localStorage.setItem("token", token) }, + SET_EXPERIMENTAL_FEATURES(state, value) { + state.currentUser.experimental_features = value + }, SET_USER_LOGOUT(state) { state.currentUser = { loggedIn: false } }, diff --git a/src/dispatch/static/dispatch/src/components/AppToolbar.vue b/src/dispatch/static/dispatch/src/components/AppToolbar.vue index f98c0e80d440..78aaad9c1a61 100644 --- a/src/dispatch/static/dispatch/src/components/AppToolbar.vue +++ b/src/dispatch/static/dispatch/src/components/AppToolbar.vue @@ -112,6 +112,16 @@ + Experimental Features + + Organizations @@ -159,6 +169,7 @@ import { mapActions, mapGetters, mapMutations, mapState } from "vuex" import Util from "@/util" import OrganizationApi from "@/organization/api" import OrganizationCreateEditDialog from "@/organization/CreateEditDialog.vue" +import UserApi from "@/auth/api" export default { name: "AppToolbar", @@ -181,6 +192,16 @@ export default { }, }, methods: { + updateExperimentalFeatures() { + UserApi.getUserInfo() + .then((response) => { + let userId = response.data.id + UserApi.update(userId, { id: userId, experimental_features: this.experimental_features }) + }) + .catch((error) => { + console.error("Error occurred while updating experimental features: ", error) + }) + }, handleDrawerToggle() { this.$store.dispatch("app/toggleDrawer") }, @@ -203,7 +224,7 @@ export default { }, ...mapState("auth", ["currentUser"]), ...mapState("app", ["currentVersion"]), - ...mapActions("auth", ["logout"]), + ...mapActions("auth", ["logout", "getExperimentalFeatures"]), ...mapActions("search", ["setQuery"]), ...mapActions("organization", ["showCreateEditDialog"]), ...mapActions("app", ["showCommitMessage"]), @@ -233,6 +254,8 @@ export default { this.organizations = response.data.items this.loading = false }) + + this.getExperimentalFeatures() }, }