1import { isEmpty, isString, set } from 'lodash'; 2import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 3import { dateTimeFormat, dateTimeFormatTimeAgo, setWeekStart, TimeZone } from '@grafana/data'; 4 5import { Team, ThunkResult, UserDTO, UserOrg, UserSession } from 'app/types'; 6import config from 'app/core/config'; 7import { contextSrv } from 'app/core/core'; 8 9export interface UserState { 10 orgId: number; 11 timeZone: TimeZone; 12 weekStart: string; 13 fiscalYearStartMonth: number; 14 user: UserDTO | null; 15 teams: Team[]; 16 orgs: UserOrg[]; 17 sessions: UserSession[]; 18 teamsAreLoading: boolean; 19 orgsAreLoading: boolean; 20 sessionsAreLoading: boolean; 21 isUpdating: boolean; 22} 23 24export const initialUserState: UserState = { 25 orgId: config.bootData.user.orgId, 26 timeZone: config.bootData.user.timezone, 27 weekStart: config.bootData.user.weekStart, 28 fiscalYearStartMonth: 0, 29 orgsAreLoading: false, 30 sessionsAreLoading: false, 31 teamsAreLoading: false, 32 isUpdating: false, 33 orgs: [], 34 sessions: [], 35 teams: [], 36 user: null, 37}; 38 39export const slice = createSlice({ 40 name: 'user/profile', 41 initialState: initialUserState, 42 reducers: { 43 updateTimeZone: (state, action: PayloadAction<{ timeZone: TimeZone }>) => { 44 state.timeZone = action.payload.timeZone; 45 }, 46 updateWeekStart: (state, action: PayloadAction<{ weekStart: string }>) => { 47 state.weekStart = action.payload.weekStart; 48 }, 49 updateFiscalYearStartMonth: (state, action: PayloadAction<{ fiscalYearStartMonth: number }>) => { 50 state.fiscalYearStartMonth = action.payload.fiscalYearStartMonth; 51 }, 52 setUpdating: (state, action: PayloadAction<{ updating: boolean }>) => { 53 state.isUpdating = action.payload.updating; 54 }, 55 userLoaded: (state, action: PayloadAction<{ user: UserDTO }>) => { 56 state.user = action.payload.user; 57 }, 58 initLoadTeams: (state, action: PayloadAction<undefined>) => { 59 state.teamsAreLoading = true; 60 }, 61 teamsLoaded: (state, action: PayloadAction<{ teams: Team[] }>) => { 62 state.teams = action.payload.teams; 63 state.teamsAreLoading = false; 64 }, 65 initLoadOrgs: (state, action: PayloadAction<undefined>) => { 66 state.orgsAreLoading = true; 67 }, 68 orgsLoaded: (state, action: PayloadAction<{ orgs: UserOrg[] }>) => { 69 state.orgs = action.payload.orgs; 70 state.orgsAreLoading = false; 71 }, 72 initLoadSessions: (state, action: PayloadAction<undefined>) => { 73 state.sessionsAreLoading = true; 74 }, 75 sessionsLoaded: (state, action: PayloadAction<{ sessions: UserSession[] }>) => { 76 const sorted = action.payload.sessions.sort((a, b) => Number(b.isActive) - Number(a.isActive)); // Show active sessions first 77 state.sessions = sorted.map((session) => ({ 78 id: session.id, 79 isActive: session.isActive, 80 seenAt: dateTimeFormatTimeAgo(session.seenAt), 81 createdAt: dateTimeFormat(session.createdAt, { format: 'MMMM DD, YYYY' }), 82 clientIp: session.clientIp, 83 browser: session.browser, 84 browserVersion: session.browserVersion, 85 os: session.os, 86 osVersion: session.osVersion, 87 device: session.device, 88 })); 89 state.sessionsAreLoading = false; 90 }, 91 userSessionRevoked: (state, action: PayloadAction<{ tokenId: number }>) => { 92 state.sessions = state.sessions.filter((session: UserSession) => { 93 return session.id !== action.payload.tokenId; 94 }); 95 state.isUpdating = false; 96 }, 97 }, 98}); 99 100export const updateFiscalYearStartMonthForSession = (fiscalYearStartMonth: number): ThunkResult<void> => { 101 return async (dispatch) => { 102 set(contextSrv, 'user.fiscalYearStartMonth', fiscalYearStartMonth); 103 dispatch(updateFiscalYearStartMonth({ fiscalYearStartMonth })); 104 }; 105}; 106 107export const updateTimeZoneForSession = (timeZone: TimeZone): ThunkResult<void> => { 108 return async (dispatch) => { 109 if (!isString(timeZone) || isEmpty(timeZone)) { 110 timeZone = config?.bootData?.user?.timezone; 111 } 112 113 set(contextSrv, 'user.timezone', timeZone); 114 dispatch(updateTimeZone({ timeZone })); 115 }; 116}; 117 118export const updateWeekStartForSession = (weekStart: string): ThunkResult<void> => { 119 return async (dispatch) => { 120 if (!isString(weekStart) || isEmpty(weekStart)) { 121 weekStart = config?.bootData?.user?.weekStart; 122 } 123 124 set(contextSrv, 'user.weekStart', weekStart); 125 dispatch(updateWeekStart({ weekStart })); 126 setWeekStart(weekStart); 127 }; 128}; 129 130export const { 131 setUpdating, 132 initLoadOrgs, 133 orgsLoaded, 134 initLoadTeams, 135 teamsLoaded, 136 userLoaded, 137 userSessionRevoked, 138 initLoadSessions, 139 sessionsLoaded, 140 updateTimeZone, 141 updateWeekStart, 142 updateFiscalYearStartMonth, 143} = slice.actions; 144 145export const userReducer = slice.reducer; 146export default { user: slice.reducer }; 147