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()
},
}