github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/redux/reducers/settings.ts (about)

     1  import { createSlice, combineReducers } from '@reduxjs/toolkit';
     2  import { Users, type User } from '@webapp/models/users';
     3  import { APIKey, APIKeys } from '@webapp/models/apikeys';
     4  import { App } from '@webapp/models/app';
     5  
     6  import {
     7    fetchUsers,
     8    createUser as createUserAPI,
     9    enableUser as enableUserAPI,
    10    disableUser as disableUserAPI,
    11    changeUserRole as changeUserRoleAPI,
    12    deleteUser as deleteUserAPI,
    13  } from '@webapp/services/users';
    14  import {
    15    fetchAPIKeys,
    16    createAPIKey as createAPIKeyAPI,
    17    deleteAPIKey as deleteAPIKeyAPI,
    18  } from '@webapp/services/apiKeys';
    19  import { fetchApps, deleteApp as deleteAppAPI } from '@webapp/services/apps';
    20  import type { RootState } from '@webapp/redux/store';
    21  import { addNotification } from './notifications';
    22  import { createAsyncThunk } from '../async-thunk';
    23  
    24  enum FetchStatus {
    25    pristine = 'pristine',
    26    loading = 'loading',
    27    loaded = 'loaded',
    28    failed = 'failed',
    29  }
    30  type DataWithStatus<T> = { type: FetchStatus; data?: T };
    31  
    32  const usersInitialState: DataWithStatus<Users> = {
    33    type: FetchStatus.pristine,
    34    data: undefined,
    35  };
    36  
    37  const apiKeysInitialState: DataWithStatus<APIKeys> = {
    38    type: FetchStatus.pristine,
    39    data: undefined,
    40  };
    41  
    42  const appsInitialState: DataWithStatus<App[]> = {
    43    type: FetchStatus.pristine,
    44    data: undefined,
    45  };
    46  
    47  export const reloadApiKeys = createAsyncThunk(
    48    'newRoot/reloadAPIKeys',
    49    async (_, thunkAPI) => {
    50      const res = await fetchAPIKeys();
    51      if (res.isOk) {
    52        return Promise.resolve(res.value);
    53      }
    54  
    55      thunkAPI.dispatch(
    56        addNotification({
    57          type: 'danger',
    58          title: 'Failed to load api keys',
    59          message: res.error.message,
    60        })
    61      );
    62  
    63      return Promise.reject(res.error);
    64    }
    65  );
    66  
    67  export const reloadUsers = createAsyncThunk(
    68    'newRoot/reloadUsers',
    69    async (_, thunkAPI) => {
    70      const res = await fetchUsers();
    71  
    72      if (res.isOk) {
    73        return Promise.resolve(res.value);
    74      }
    75  
    76      thunkAPI.dispatch(
    77        addNotification({
    78          type: 'danger',
    79          title: 'Failed to load users',
    80          message: res.error.message,
    81        })
    82      );
    83  
    84      return Promise.reject(res.error);
    85    }
    86  );
    87  
    88  export const reloadApps = createAsyncThunk(
    89    'newRoot/reloadApps',
    90    async (_, thunkAPI) => {
    91      const res = await fetchApps();
    92  
    93      if (res.isOk) {
    94        return Promise.resolve(res.value);
    95      }
    96  
    97      // eslint-disable-next-line @typescript-eslint/no-floating-promises
    98      thunkAPI.dispatch(
    99        addNotification({
   100          type: 'danger',
   101          title: 'Failed to load apps',
   102          message: res.error.message,
   103        })
   104      );
   105  
   106      return Promise.reject(res.error);
   107    }
   108  );
   109  
   110  export const enableUser = createAsyncThunk(
   111    'newRoot/enableUser',
   112    async (user: User, thunkAPI) => {
   113      const res = await enableUserAPI(user);
   114  
   115      if (res.isOk) {
   116        thunkAPI.dispatch(reloadUsers());
   117        return Promise.resolve(true);
   118      }
   119  
   120      thunkAPI.dispatch(
   121        addNotification({
   122          type: 'danger',
   123          title: 'Failed to enable a user',
   124          message: res.error.message,
   125        })
   126      );
   127  
   128      return Promise.reject(res.error);
   129    }
   130  );
   131  
   132  export const disableUser = createAsyncThunk(
   133    'newRoot/disableUser',
   134    async (user: User, thunkAPI) => {
   135      const res = await disableUserAPI(user);
   136  
   137      if (res.isOk) {
   138        thunkAPI.dispatch(reloadUsers());
   139        return Promise.resolve(true);
   140      }
   141  
   142      thunkAPI.dispatch(
   143        addNotification({
   144          type: 'danger',
   145          title: 'Failed to disable a user',
   146          message: res.error.message,
   147        })
   148      );
   149  
   150      return Promise.reject(res.error);
   151    }
   152  );
   153  
   154  export const createUser = createAsyncThunk(
   155    'newRoot/createUser',
   156    async (user: User, thunkAPI) => {
   157      const res = await createUserAPI(user);
   158  
   159      thunkAPI.dispatch(reloadUsers());
   160  
   161      if (res.isOk) {
   162        return Promise.resolve(true);
   163      }
   164  
   165      thunkAPI.dispatch(
   166        addNotification({
   167          type: 'danger',
   168          title: 'Failed to create new user',
   169          message: res.error.message,
   170        })
   171      );
   172      return Promise.reject(res.error);
   173    }
   174  );
   175  
   176  export const deleteUser = createAsyncThunk(
   177    'newRoot/deleteUser',
   178    async (user: User, thunkAPI) => {
   179      const res = await deleteUserAPI({ id: user.id });
   180  
   181      thunkAPI.dispatch(reloadUsers());
   182  
   183      if (res.isOk) {
   184        return Promise.resolve(true);
   185      }
   186  
   187      thunkAPI.dispatch(
   188        addNotification({
   189          type: 'danger',
   190          title: 'Failed to delete user',
   191          message: res.error.message,
   192        })
   193      );
   194      return Promise.reject(res.error);
   195    }
   196  );
   197  
   198  export const changeUserRole = createAsyncThunk(
   199    'users/changeUserRole',
   200    async (action: Pick<User, 'id' | 'role'>, thunkAPI) => {
   201      const { id, role } = action;
   202      const res = await changeUserRoleAPI(id, role);
   203  
   204      if (res.isOk) {
   205        return Promise.resolve(true);
   206      }
   207  
   208      thunkAPI.dispatch(
   209        addNotification({
   210          type: 'danger',
   211          title: 'Failed to change users role',
   212          message: res.error.message,
   213        })
   214      );
   215      return thunkAPI.rejectWithValue(res.error);
   216    }
   217  );
   218  
   219  export const createAPIKey = createAsyncThunk(
   220    'newRoot/createAPIKey',
   221    async (data: Parameters<typeof createAPIKeyAPI>[0], thunkAPI) => {
   222      const res = await createAPIKeyAPI(data);
   223  
   224      if (res.isOk) {
   225        return Promise.resolve(res.value);
   226      }
   227  
   228      thunkAPI.dispatch(
   229        addNotification({
   230          type: 'danger',
   231          title: 'Failed to create API key',
   232          message: res.error.message,
   233        })
   234      );
   235      return thunkAPI.rejectWithValue(res.error);
   236    }
   237  );
   238  
   239  export const deleteAPIKey = createAsyncThunk(
   240    'newRoot/deleteAPIKey',
   241    async (data: Pick<APIKey, 'id'>, thunkAPI) => {
   242      const res = await deleteAPIKeyAPI(data);
   243      if (res.isOk) {
   244        thunkAPI.dispatch(
   245          addNotification({
   246            type: 'success',
   247            title: 'Key has been deleted',
   248            message: `API Key id ${data.id} has been successfully deleted`,
   249          })
   250        );
   251        return Promise.resolve(true);
   252      }
   253  
   254      thunkAPI.dispatch(
   255        addNotification({
   256          type: 'danger',
   257          title: 'Failed to delete API key',
   258          message: res.error.message,
   259        })
   260      );
   261      return thunkAPI.rejectWithValue(res.error);
   262    }
   263  );
   264  
   265  export const deleteApp = createAsyncThunk(
   266    'newRoot/deleteApp',
   267    async (app: App, thunkAPI) => {
   268      const res = await deleteAppAPI({ name: app.name });
   269  
   270      // eslint-disable-next-line @typescript-eslint/no-floating-promises
   271      thunkAPI.dispatch(reloadApps());
   272  
   273      if (res.isOk) {
   274        return Promise.resolve(true);
   275      }
   276  
   277      // eslint-disable-next-line @typescript-eslint/no-floating-promises
   278      thunkAPI.dispatch(
   279        addNotification({
   280          type: 'danger',
   281          title: 'Failed to delete app',
   282          message: res.error.message,
   283        })
   284      );
   285      return Promise.reject(res.error);
   286    }
   287  );
   288  
   289  export const usersSlice = createSlice({
   290    name: 'users',
   291    initialState: usersInitialState,
   292    reducers: {},
   293    extraReducers: (builder) => {
   294      builder.addCase(reloadUsers.fulfilled, (state, action) => {
   295        return { type: FetchStatus.loaded, data: action.payload };
   296      });
   297  
   298      builder.addCase(reloadUsers.pending, (state) => {
   299        return { type: FetchStatus.loading, data: state.data };
   300      });
   301      builder.addCase(reloadUsers.rejected, (state) => {
   302        return { type: FetchStatus.failed, data: state.data };
   303      });
   304    },
   305  });
   306  
   307  export const apiKeysSlice = createSlice({
   308    name: 'apiKeys',
   309    initialState: apiKeysInitialState,
   310    reducers: {},
   311    extraReducers: (builder) => {
   312      builder.addCase(reloadApiKeys.fulfilled, (_, action) => {
   313        return { type: FetchStatus.loaded, data: action.payload };
   314      });
   315      builder.addCase(reloadApiKeys.pending, (state) => {
   316        return { type: FetchStatus.loading, data: state.data };
   317      });
   318      builder.addCase(reloadApiKeys.rejected, (state) => {
   319        return { type: FetchStatus.failed, data: state.data };
   320      });
   321    },
   322  });
   323  
   324  export const appsSlice = createSlice({
   325    name: 'apps',
   326    initialState: appsInitialState,
   327    reducers: {},
   328    extraReducers: (builder) => {
   329      builder.addCase(reloadApps.fulfilled, (_, action) => {
   330        return { type: FetchStatus.loaded, data: action.payload };
   331      });
   332      builder.addCase(reloadApps.pending, (state) => {
   333        return { type: FetchStatus.loading, data: state.data };
   334      });
   335      builder.addCase(reloadApps.rejected, (state) => {
   336        return { type: FetchStatus.failed, data: state.data };
   337      });
   338    },
   339  });
   340  
   341  export const settingsState = (state: RootState) => state.settings;
   342  
   343  export const usersState = (state: RootState) => state.settings.users;
   344  export const selectUsers = (state: RootState) => state.settings.users.data;
   345  
   346  export const apiKeysState = (state: RootState) => state.settings.apiKeys;
   347  export const selectAPIKeys = (state: RootState) => state.settings.apiKeys.data;
   348  
   349  export const appsState = (state: RootState) => state.settings.apps;
   350  export const selectApps = (state: RootState) => state.settings.apps.data;
   351  export const selectIsLoadingApps = (state: RootState) => {
   352    return state.settings.apps.type === FetchStatus.loading;
   353  };
   354  
   355  export default combineReducers({
   356    users: usersSlice.reducer,
   357    apiKeys: apiKeysSlice.reducer,
   358    apps: appsSlice.reducer,
   359  });