From 4ab7c8281c11d0abc743fa96cffa0c51d1f3216c Mon Sep 17 00:00:00 2001 From: SangLv Date: Tue, 25 Jul 2023 13:21:46 +0700 Subject: [PATCH 01/29] integrate API --- frontend/package.json | 4 +- frontend/src/api/Workspace/index.ts | 26 ++ frontend/src/api/users/UsersApiDTO.ts | 1 + frontend/src/pages/Workspace/index.tsx | 222 +++++++++++++----- .../slice/Workspace/WorkspaceSelector.ts | 1 + .../store/slice/Workspace/WorkspaceSlice.ts | 47 +++- .../store/slice/Workspace/WorkspaceType.ts | 25 ++ .../slice/Workspace/WorkspacesActions.ts | 56 +++++ frontend/yarn.lock | 78 +++++- 9 files changed, 394 insertions(+), 66 deletions(-) create mode 100644 frontend/src/api/Workspace/index.ts create mode 100644 frontend/src/store/slice/Workspace/WorkspacesActions.ts diff --git a/frontend/package.json b/frontend/package.json index d0f0b1aca..48c44cb5a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "@mui/lab": "^5.0.0-alpha.68", "@mui/material": "^5.4.1", "@mui/x-data-grid": "^5.5.1", + "@mui/x-data-grid-pro": "^6.10.1", "@reduxjs/toolkit": "^1.6.1", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", @@ -73,7 +74,8 @@ }, "lint-staged": { "*.{ts,tsx}": [ - "prettier --write" + "prettier --write", + "eslint --fix" ] }, "prettier": { diff --git a/frontend/src/api/Workspace/index.ts b/frontend/src/api/Workspace/index.ts new file mode 100644 index 000000000..76cb53863 --- /dev/null +++ b/frontend/src/api/Workspace/index.ts @@ -0,0 +1,26 @@ +import { WorkspaceParams } from "store/slice/Workspace/WorkspaceType" +import axios from "utils/axios" + +export type Data = { + name: string +} + +export const getWorkspacesApi = async () => { + const response = await axios.get(`/workspaces`) + return response.data +} + +export const delWorkspaceApi = async (id: number) => { + const response = await axios.delete(`/workspace/${id}`) + return response.data +} + +export const postWorkspaceApi = async (data: Data) => { + const response = await axios.post(`/workspace`, data) + return response.data +} + +export const putWorkspaceApi = async (data: Data) => { + const response = await axios.put(`/workspace`, data) + return response.data +} \ No newline at end of file diff --git a/frontend/src/api/users/UsersApiDTO.ts b/frontend/src/api/users/UsersApiDTO.ts index 114847bec..416a90d83 100644 --- a/frontend/src/api/users/UsersApiDTO.ts +++ b/frontend/src/api/users/UsersApiDTO.ts @@ -1,4 +1,5 @@ export type UserDTO = { + id: number uid: string email: string } diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 7d1866b12..fafb12939 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -1,30 +1,39 @@ -// import { useEffect } from 'react' -import { useSelector /*, useDispatch */ } from 'react-redux' -import { Box, styled, Button, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material' +import { useSelector, useDispatch } from 'react-redux' +import { Box, styled, Button, Dialog, DialogTitle, DialogContent, DialogActions, Input } from '@mui/material' import { - DataGrid, - GridColDef, + GridActionsCellItem, GridRenderCellParams, + GridRowModes, GridRowParams, } from '@mui/x-data-grid' -import { Link } from 'react-router-dom' +import { DataGridPro } from '@mui/x-data-grid-pro' +import { Link, useSearchParams } from 'react-router-dom' +import { selectCurrentUser } from 'store/slice/User/UserSelector' import Loading from '../../components/common/Loading' import { - selectIsLoadingWorkspaceList, - // selectWorkspaceList, + selectIsLoadingWorkspaceList, selectWorkspaceData, } from 'store/slice/Workspace/WorkspaceSelector' import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt'; import CancelIcon from '@mui/icons-material/Cancel'; -import { useState } from "react"; +import { ChangeEvent, useEffect, useState } from "react"; import GroupsIcon from '@mui/icons-material/Groups'; import EditIcon from '@mui/icons-material/Edit'; +import { delWorkspace, getWorkspaceList, postWorkspace, putWorkspace } from 'store/slice/Workspace/WorkspacesActions' type PopupType = { open: boolean handleClose: () => void + handleOkDel?: () => void + setNewWorkSpace?: (name: string) => void + value?: string + handleOkNew?: () => void + handleOkSave?: () => void } -const columns = (handleOpenPopupShare: () => void, handleOpenPopupDel: () => void) => ( +const columns = ( + handleOpenPopupShare: () => void, + handleOpenPopupDel: (id: number) => void, + ) => ( [ { field: 'id', @@ -53,7 +62,7 @@ const columns = (handleOpenPopupShare: () => void, handleOpenPopupDel: () => voi ), }, { - field: 'owner', + field: 'user', headerName: 'Owner', minWidth: 200, renderCell: (params: GridRenderCellParams) => ( @@ -66,7 +75,7 @@ const columns = (handleOpenPopupShare: () => void, handleOpenPopupDel: () => voi ), }, { - field: 'created', + field: 'created_at', headerName: 'Created', minWidth: 200, renderCell: (params: GridRenderCellParams) => ( @@ -120,7 +129,7 @@ const columns = (handleOpenPopupShare: () => void, handleOpenPopupDel: () => voi minWidth: 130, renderCell: (params: GridRenderCellParams) => ( params.row.owner !== "User 2" ? - + handleOpenPopupDel(params.row.id)}> Del : "" ), @@ -170,30 +179,6 @@ const columnsShare = (handleShareFalse: (parmas: GridRenderCellParams) = ] ) -const data = [ - { - id: 1, - owner: "User 1", - name: "Name 1", - created: "YYYY/MM/DD HH:MI", - share: false - }, - { - id: 2, - owner: "User 2", - name: "Name 2", - created: "YYYY/MM/DD HH:MI", - share: true - }, - { - id: 3, - owner: "User 1", - name: "Name 3", - created: "YYYY/MM/DD HH:MI", - share: true - } -] - const dataShare = [ { id: 1, @@ -220,7 +205,6 @@ const dataShare = [ const PopupShare = ({open, handleClose}: PopupType) => { const [tableShare, setTableShare] = useState(dataShare) - if(!open) return <> const handleShareTrue = (params: GridRowParams) => { if(params.row.share) return const index = tableShare.findIndex(item => item.id === params.id) @@ -249,11 +233,11 @@ const PopupShare = ({open, handleClose}: PopupType) => { Share Workspace アクセス許可ユーザー - @@ -265,10 +249,39 @@ const PopupShare = ({open, handleClose}: PopupType) => { ) } -const PopupDelete = ({open, handleClose}: PopupType) => { - if(!open) return <> +const PopupNew = ({open, handleClose, value, setNewWorkSpace, handleOkNew}: PopupType) => { + if(!setNewWorkSpace) return <> + const handleName = (event: ChangeEvent) => { + setNewWorkSpace(event.target.value) + } + return ( + + + Create New Workspace + + + + + + + + + + ) +} +const PopupDelete = ({open, handleClose, handleOkDel}: PopupType) => { + return ( { Do you want delete? - + ) } +const PopupSave = ({open, handleClose, handleOkSave}: PopupType) => { + return ( + + + Do you want save? + + + + + + + ) +} + const Workspaces = () => { - // const dispatch = useDispatch() - // const workspaces = useSelector(selectWorkspaceList) + const dispatch = useDispatch() const loading = useSelector(selectIsLoadingWorkspaceList) - const [openShare, setOpenShare] = useState(false) - const [openDel, setOpenDel] = useState(false) + const data = useSelector(selectWorkspaceData) + const user = useSelector(selectCurrentUser) + const [open, setOpen] = useState({share: false, del: false, new: false, save: false}) + const [idDel, setIdDel] = useState() + const [newWorkspace, setNewWorkSpace] = useState() + const [rowModesModel, setRowModesModel] = useState({}); + const [nameEdit, setNameEdit] = useState("") + const [searchParams, setParams] = useSearchParams() - /* TODO: Add get workspace apis and actions useEffect(() => { dispatch(getWorkspaceList()) - //eslint-disable-next-line }, []) - */ + const handleOpenPopupShare = () => { - setOpenShare(false) + setOpen({...open, share: true}) } const handleClosePopupShare = () => { - setOpenShare(false) + setOpen({...open, share: false}) } - const handleOpenPopupDel = () => { - setOpenDel(true) + const handleOpenPopupDel = (id: number) => { + setIdDel(id) + setOpen({...open, del: true}) + } + + const handleOkDel = async () => { + if(!idDel) return + await dispatch(delWorkspace(idDel)) + setOpen({...open, del: false}) } const handleClosePopupDel = () => { - setOpenDel(false) + setOpen({...open, del: false}) + } + + const handleOpenPopupNew = () => { + setOpen({...open, new: true}) } + const handleClosePopupNew = () => { + setOpen({...open, new: false}) + } + + const handleClosePopupSave = () => { + setOpen({...open, save: false}) + } + + const handleOkSave = async () => { + await dispatch(putWorkspace({name: nameEdit})) + setOpen({...open, save: false}) + } + + const handleOkNew = async () => { + if(!newWorkspace) { + setOpen({...open, new: false}) + return + } + await dispatch(postWorkspace({name: newWorkspace})) + setOpen({...open, new: false}) + } + + const handleEditstop = () => { + setOpen({...open, save: true}) + } + + const handleRowModesModelChange = (e: any) => { + console.log(e) + } + + const processRowUpdate = (newRow: any) => { + setNameEdit(newRow?.name) + // const updatedRow = { ...newRow, isNew: false }; + // setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row))); + return newRow; + }; + return ( Workspaces @@ -326,22 +408,36 @@ const Workspaces = () => { }} > Import - New + New - params.row.owner === "User 1"} + rows={data?.items} + editMode="row" + columns={columns(handleOpenPopupShare, handleOpenPopupDel) as any} + isCellEditable={(params) => params.row.user?.id === user?.id} + onRowEditStop={handleEditstop} + processRowUpdate={processRowUpdate} + onRowModesModelChange={handleRowModesModelChange} + /> + + + + {loading ? : null} - - ) } const WorkspacesWrapper = styled(Box)(({ theme }) => ({ + margin: "auto", + width: "90vw", padding: theme.spacing(2), overflow: 'auto', })) diff --git a/frontend/src/store/slice/Workspace/WorkspaceSelector.ts b/frontend/src/store/slice/Workspace/WorkspaceSelector.ts index aa69430ef..db4fa182b 100644 --- a/frontend/src/store/slice/Workspace/WorkspaceSelector.ts +++ b/frontend/src/store/slice/Workspace/WorkspaceSelector.ts @@ -1,6 +1,7 @@ import { RootState } from 'store/store' export const selectWorkspace = (state: RootState) => state.workspace +export const selectWorkspaceData = (state: RootState) => state.workspace.data export const selectActiveTab = (state: RootState) => state.workspace.currentWorkspace.selectedTab diff --git a/frontend/src/store/slice/Workspace/WorkspaceSlice.ts b/frontend/src/store/slice/Workspace/WorkspaceSlice.ts index ecd8e261a..7468ee08f 100644 --- a/frontend/src/store/slice/Workspace/WorkspaceSlice.ts +++ b/frontend/src/store/slice/Workspace/WorkspaceSlice.ts @@ -1,12 +1,19 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit' import { WORKSPACE_SLICE_NAME, Workspace } from './WorkspaceType' import { importExperimentByUid } from '../Experiments/ExperimentsActions' +import {delWorkspace, getWorkspaceList, postWorkspace, putWorkspace } from './WorkspacesActions' const initialState: Workspace = { workspaces: [{ workspace_id: 'default' }], currentWorkspace: { selectedTab: 0, }, + data: { + items: [], + total: 0, + limit: 50, + offset: 0 + }, loading: false, } @@ -27,9 +34,47 @@ export const workspaceSlice = createSlice({ }, }, extraReducers(builder) { - builder.addCase(importExperimentByUid.fulfilled, (state, action) => { + builder + .addCase(importExperimentByUid.fulfilled, (state, action) => { state.currentWorkspace.workspaceId = action.meta.arg.workspaceId }) + .addCase(getWorkspaceList.pending, (state, action) => { + state.loading = true + }) + .addCase(getWorkspaceList.fulfilled, (state, action) => { + state.data = action.payload + state.loading = false + }) + .addCase(getWorkspaceList.rejected, (state, action) => { + state.loading = false + }) + .addCase(postWorkspace.pending, (state, action) => { + state.loading = true + }) + .addCase(postWorkspace.fulfilled, (state, action) => { + state.loading = false + }) + .addCase(postWorkspace.rejected, (state, action) => { + state.loading = false + }) + .addCase(putWorkspace.pending, (state, action) => { + state.loading = true + }) + .addCase(putWorkspace.fulfilled, (state, action) => { + state.loading = false + }) + .addCase(putWorkspace.rejected, (state, action) => { + state.loading = false + }) + .addCase(delWorkspace.pending, (state, action) => { + state.loading = true + }) + .addCase(delWorkspace.fulfilled, (state, action) => { + state.loading = false + }) + .addCase(delWorkspace.rejected, (state, action) => { + state.loading = false + }) // TODO: add case for set loading on get workspaces pending }, }) diff --git a/frontend/src/store/slice/Workspace/WorkspaceType.ts b/frontend/src/store/slice/Workspace/WorkspaceType.ts index 2bfddf561..469bb64f9 100644 --- a/frontend/src/store/slice/Workspace/WorkspaceType.ts +++ b/frontend/src/store/slice/Workspace/WorkspaceType.ts @@ -1,11 +1,34 @@ export const WORKSPACE_SLICE_NAME = 'workspace' +export type ItemsWorkspace = { + id: number + name: string + user: { + id: number + name: string + email: string + created_at: string + updated_at: string + }, + created_at: string + updated_at: string +} + +export type WorkspaceDataDTO = { + items: ItemsWorkspace[], + total: number + limit: number + offset: number +} + export type Workspace = { workspaces: WorkspaceType[] currentWorkspace: { workspaceId?: string selectedTab: number } + data: WorkspaceDataDTO + loading: boolean } @@ -13,3 +36,5 @@ export type WorkspaceType = { workspace_id: string // TODO: add fields required for workspace } + +export type WorkspaceParams = { [key: string]: string | undefined | number | string[] } diff --git a/frontend/src/store/slice/Workspace/WorkspacesActions.ts b/frontend/src/store/slice/Workspace/WorkspacesActions.ts new file mode 100644 index 000000000..c2389f6e6 --- /dev/null +++ b/frontend/src/store/slice/Workspace/WorkspacesActions.ts @@ -0,0 +1,56 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import {delWorkspaceApi, getWorkspacesApi, postWorkspaceApi, putWorkspaceApi } from "api/Workspace" +import {ItemsWorkspace, WorkspaceDataDTO, WorkspaceParams, WORKSPACE_SLICE_NAME } from "./WorkspaceType" + +// @ts-ignore +export const getWorkspaceList = createAsyncThunk(`${WORKSPACE_SLICE_NAME}/getWorkspaceList`, async () => { + // const { rejectWithValue } = thunkAPI + try { + const response = await getWorkspacesApi() + return response + } catch (e) { + return null + } +}) + +export const delWorkspace = createAsyncThunk< + boolean, + number +>(`${WORKSPACE_SLICE_NAME}/delWorkspaceList`, async (id, thunkAPI) => { + const { rejectWithValue,dispatch } = thunkAPI + try { + const response = await delWorkspaceApi(id) + await dispatch(getWorkspaceList()) + return response + } catch (e) { + return rejectWithValue(e) + } +}) + +export const postWorkspace = createAsyncThunk< + ItemsWorkspace, + { name: string } +>(`${WORKSPACE_SLICE_NAME}/postWorkspaceList`, async (data, thunkAPI) => { + const { rejectWithValue, dispatch } = thunkAPI + try { + const response = await postWorkspaceApi(data) + await dispatch(getWorkspaceList()) + return response + } catch (e) { + return rejectWithValue(e) + } +}) + +export const putWorkspace = createAsyncThunk< + ItemsWorkspace, + { name: string } +>(`${WORKSPACE_SLICE_NAME}/putWorkspaceList`, async (data, thunkAPI) => { + const { rejectWithValue, dispatch } = thunkAPI + try { + const response = await putWorkspaceApi(data) + await dispatch(getWorkspaceList()) + return response + } catch (e) { + return rejectWithValue(e) + } +}) \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1510afd12..afd354c53 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1049,7 +1049,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.15.4", "@babel/runtime@^7.18.9": +"@babel/runtime@^7.15.4", "@babel/runtime@^7.18.9", "@babel/runtime@^7.22.5", "@babel/runtime@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== @@ -1690,6 +1690,17 @@ resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.1.tgz#9cf159dc60a101ee336e6ec74193a4f5f97f6160" integrity sha512-33hbHFLCwenTpS+T4m4Cz7cQ/ng5g+IgtINkw1uDBVvi1oM83VNt/IGzWIQNPK8H2pr0WIfkmboD501bVdYsPw== +"@mui/utils@^5.13.6", "@mui/utils@^5.13.7": + version "5.14.1" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.1.tgz#29696371016552a6eb3af975bc7af429ec88b29a" + integrity sha512-39KHKK2JeqRmuUcLDLwM+c2XfVC136C5/yUyQXmO2PVbOb2Bol4KxtkssEqCbTwg87PSCG3f1Tb0keRsK7cVGw== + dependencies: + "@babel/runtime" "^7.22.6" + "@types/prop-types" "^15.7.5" + "@types/react-is" "^18.2.1" + prop-types "^15.8.1" + react-is "^18.2.0" + "@mui/utils@^5.4.1": version "5.4.1" resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.4.1.tgz#feb365ce9a4426587510f0943fd6d6e1889e06e6" @@ -1701,6 +1712,31 @@ prop-types "^15.7.2" react-is "^17.0.2" +"@mui/x-data-grid-pro@^6.10.1": + version "6.10.1" + resolved "https://registry.yarnpkg.com/@mui/x-data-grid-pro/-/x-data-grid-pro-6.10.1.tgz#602ed86d03a86003504549a1b1881340eb3bd0d5" + integrity sha512-ynwy/IZQHAZSua+tDdJzhMEiOev+iMWcJ/1Y+q0qGco1PtBhsMRM/d671KQyJxvVy+nOtH21eYA0XtrxjE4+kw== + dependencies: + "@babel/runtime" "^7.22.6" + "@mui/utils" "^5.13.7" + "@mui/x-data-grid" "6.10.1" + "@mui/x-license-pro" "6.10.0" + "@types/format-util" "^1.0.2" + clsx "^1.2.1" + prop-types "^15.8.1" + reselect "^4.1.8" + +"@mui/x-data-grid@6.10.1": + version "6.10.1" + resolved "https://registry.yarnpkg.com/@mui/x-data-grid/-/x-data-grid-6.10.1.tgz#cd79bd326cffff0bb368b8230fd690574e95ab32" + integrity sha512-CulrMp8K9os1d4mCPidotE/oYhgYlWoz+hpQs/OI8gD/dB+A7VJEjG5yEgYqH04jGl1ne9zLg4p6oEu55VJhYQ== + dependencies: + "@babel/runtime" "^7.22.6" + "@mui/utils" "^5.13.7" + clsx "^1.2.1" + prop-types "^15.8.1" + reselect "^4.1.8" + "@mui/x-data-grid@^5.5.1": version "5.5.1" resolved "https://registry.yarnpkg.com/@mui/x-data-grid/-/x-data-grid-5.5.1.tgz#7e0aafba7f28006057d7b7437c13d7609a958097" @@ -1711,6 +1747,14 @@ prop-types "^15.8.1" reselect "^4.1.5" +"@mui/x-license-pro@6.10.0": + version "6.10.0" + resolved "https://registry.yarnpkg.com/@mui/x-license-pro/-/x-license-pro-6.10.0.tgz#22ceb48dfded0a6bbdcd640e45c73d124e78cab1" + integrity sha512-Dvvn2U/P0rjHdiEWKWU77DrboZMXyNSj0S2009Mylot9oc1JK70wIKF9rIMHS4OkMtfZWetu11+HQKUY4tVkrQ== + dependencies: + "@babel/runtime" "^7.22.5" + "@mui/utils" "^5.13.6" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2349,6 +2393,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/format-util@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/format-util/-/format-util-1.0.2.tgz#7d19feb1caf59b6ea99c83dfe795ffa3f06b87d7" + integrity sha512-9SrLCpgzWo2yHHhiMOX0WwgDh37nSbDbWUsRc1ss++o8O97E3tB6SJiyUQM21UeUsKvZNuhDCmkRaINZ4uJAfg== + "@types/geojson@*": version "7946.0.10" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.10.tgz#6dfbf5ea17142f7f9a043809f1cd4c448cb68249" @@ -2469,6 +2518,11 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== +"@types/prop-types@^15.7.5": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + "@types/q@^1.5.1": version "1.5.5" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" @@ -2496,6 +2550,13 @@ dependencies: "@types/react" "*" +"@types/react-is@^18.2.1": + version "18.2.1" + resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-18.2.1.tgz#61d01c2a6fc089a53520c0b66996d458fdc46863" + integrity sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw== + dependencies: + "@types/react" "*" + "@types/react-redux@^7.1.20", "@types/react-redux@^7.1.25": version "7.1.25" resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88" @@ -4150,6 +4211,11 @@ clsx@^1.1.0, clsx@^1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -11111,6 +11177,11 @@ react-is@^17.0.1, react-is@^17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-is@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + react-linear-gradient-picker@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/react-linear-gradient-picker/-/react-linear-gradient-picker-2.0.3.tgz#9fcd0eb9bb388c5fe41cf4f2b74fef2d65241162" @@ -11573,6 +11644,11 @@ reselect@^4.1.5: resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.5.tgz#852c361247198da6756d07d9296c2b51eddb79f6" integrity sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ== +reselect@^4.1.8: + version "4.1.8" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" + integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" From 629f9d41d99ee3de613e8bec15573d814dab9293 Mon Sep 17 00:00:00 2001 From: SangLv Date: Tue, 25 Jul 2023 17:30:04 +0700 Subject: [PATCH 02/29] integrate edit name --- frontend/src/api/Workspace/index.ts | 6 +- frontend/src/pages/Workspace/index.tsx | 72 ++++++++++--------- .../slice/Workspace/WorkspacesActions.ts | 4 +- 3 files changed, 43 insertions(+), 39 deletions(-) diff --git a/frontend/src/api/Workspace/index.ts b/frontend/src/api/Workspace/index.ts index 76cb53863..e7a77a345 100644 --- a/frontend/src/api/Workspace/index.ts +++ b/frontend/src/api/Workspace/index.ts @@ -1,8 +1,8 @@ -import { WorkspaceParams } from "store/slice/Workspace/WorkspaceType" import axios from "utils/axios" export type Data = { - name: string + name?: string + id?: number } export const getWorkspacesApi = async () => { @@ -21,6 +21,6 @@ export const postWorkspaceApi = async (data: Data) => { } export const putWorkspaceApi = async (data: Data) => { - const response = await axios.put(`/workspace`, data) + const response = await axios.put(`/workspace/${data.id}`, {name: data.name}) return response.data } \ No newline at end of file diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index fafb12939..63d7b62e1 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -1,13 +1,12 @@ import { useSelector, useDispatch } from 'react-redux' import { Box, styled, Button, Dialog, DialogTitle, DialogContent, DialogActions, Input } from '@mui/material' import { - GridActionsCellItem, GridRenderCellParams, - GridRowModes, GridRowParams, + DataGrid } from '@mui/x-data-grid' -import { DataGridPro } from '@mui/x-data-grid-pro' -import { Link, useSearchParams } from 'react-router-dom' +import { DataGridPro, GridRowModesModel } from '@mui/x-data-grid-pro' +import { Link } from 'react-router-dom' import { selectCurrentUser } from 'store/slice/User/UserSelector' import Loading from '../../components/common/Loading' import { @@ -16,8 +15,8 @@ import { import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt'; import CancelIcon from '@mui/icons-material/Cancel'; import { ChangeEvent, useEffect, useState } from "react"; -import GroupsIcon from '@mui/icons-material/Groups'; import EditIcon from '@mui/icons-material/Edit'; +import PeopleOutlineIcon from '@mui/icons-material/PeopleOutline'; import { delWorkspace, getWorkspaceList, postWorkspace, putWorkspace } from 'store/slice/Workspace/WorkspacesActions' type PopupType = { @@ -28,6 +27,7 @@ type PopupType = { value?: string handleOkNew?: () => void handleOkSave?: () => void + error?: string } const columns = ( @@ -70,7 +70,7 @@ const columns = ( sx={{display: "flex", alignItems: "center", gap: 2}} > {params.value} - {params.value === "User 2" ? : ""} + {params.value === "User 2" ? : ""} ), }, @@ -233,9 +233,9 @@ const PopupShare = ({open, handleClose}: PopupType) => { Share Workspace アクセス許可ユーザー - @@ -249,7 +249,14 @@ const PopupShare = ({open, handleClose}: PopupType) => { ) } -const PopupNew = ({open, handleClose, value, setNewWorkSpace, handleOkNew}: PopupType) => { +const PopupNew = ({ + open, + handleClose, + value, + setNewWorkSpace, + handleOkNew, + error +}: PopupType) => { if(!setNewWorkSpace) return <> const handleName = (event: ChangeEvent) => { setNewWorkSpace(event.target.value) @@ -270,6 +277,8 @@ const PopupNew = ({open, handleClose, value, setNewWorkSpace, handleOkNew}: Popu value={value || ""} onChange={handleName} /> +
+ {error ? {error} : null}
@@ -284,9 +293,9 @@ const PopupDelete = ({open, handleClose, handleOkDel}: PopupType) => { return ( Do you want delete? @@ -302,9 +311,9 @@ const PopupSave = ({open, handleClose, handleOkSave}: PopupType) => { return ( Do you want save? @@ -324,12 +333,12 @@ const Workspaces = () => { const [open, setOpen] = useState({share: false, del: false, new: false, save: false}) const [idDel, setIdDel] = useState() const [newWorkspace, setNewWorkSpace] = useState() - const [rowModesModel, setRowModesModel] = useState({}); - const [nameEdit, setNameEdit] = useState("") - const [searchParams, setParams] = useSearchParams() + const [dataEdit, setDataEdit] = useState<{name?: string, id?: number}>() + const [error, setError] = useState("") useEffect(() => { dispatch(getWorkspaceList()) + //eslint-disable-next-line }, []) const handleOpenPopupShare = () => { @@ -368,31 +377,27 @@ const Workspaces = () => { } const handleOkSave = async () => { - await dispatch(putWorkspace({name: nameEdit})) + if(!dataEdit) return + await dispatch(putWorkspace({name: dataEdit.name, id: dataEdit.id})) setOpen({...open, save: false}) } const handleOkNew = async () => { if(!newWorkspace) { - setOpen({...open, new: false}) + setError("is not empty") return } await dispatch(postWorkspace({name: newWorkspace})) setOpen({...open, new: false}) } - const handleEditstop = () => { - setOpen({...open, save: true}) - } - - const handleRowModesModelChange = (e: any) => { - console.log(e) - } - const processRowUpdate = (newRow: any) => { - setNameEdit(newRow?.name) - // const updatedRow = { ...newRow, isNew: false }; - // setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row))); + if(!newRow?.name) { + alert("is not empty") + return + } + setOpen({...open, save: true}) + setDataEdit({id: newRow?.id, name: newRow?.name}) return newRow; }; @@ -415,10 +420,8 @@ const Workspaces = () => { rows={data?.items} editMode="row" columns={columns(handleOpenPopupShare, handleOpenPopupDel) as any} - isCellEditable={(params) => params.row.user?.id === user?.id} - onRowEditStop={handleEditstop} + isCellEditable={(params) => /*params.row.user?.id === user?.id*/ !!params } processRowUpdate={processRowUpdate} - onRowModesModelChange={handleRowModesModelChange} /> @@ -428,6 +431,7 @@ const Workspaces = () => { handleClose={handleClosePopupNew} setNewWorkSpace={setNewWorkSpace} value={newWorkspace} + error={error} handleOkNew={handleOkNew} /> {loading ? : null} diff --git a/frontend/src/store/slice/Workspace/WorkspacesActions.ts b/frontend/src/store/slice/Workspace/WorkspacesActions.ts index c2389f6e6..7a6130435 100644 --- a/frontend/src/store/slice/Workspace/WorkspacesActions.ts +++ b/frontend/src/store/slice/Workspace/WorkspacesActions.ts @@ -1,6 +1,6 @@ import { createAsyncThunk } from "@reduxjs/toolkit" import {delWorkspaceApi, getWorkspacesApi, postWorkspaceApi, putWorkspaceApi } from "api/Workspace" -import {ItemsWorkspace, WorkspaceDataDTO, WorkspaceParams, WORKSPACE_SLICE_NAME } from "./WorkspaceType" +import {ItemsWorkspace, WORKSPACE_SLICE_NAME } from "./WorkspaceType" // @ts-ignore export const getWorkspaceList = createAsyncThunk(`${WORKSPACE_SLICE_NAME}/getWorkspaceList`, async () => { @@ -43,7 +43,7 @@ export const postWorkspace = createAsyncThunk< export const putWorkspace = createAsyncThunk< ItemsWorkspace, - { name: string } + { name?: string, id?: number } >(`${WORKSPACE_SLICE_NAME}/putWorkspaceList`, async (data, thunkAPI) => { const { rejectWithValue, dispatch } = thunkAPI try { From ae7ead1b00bf276a7c90d9c9d871bb36f62b41f4 Mon Sep 17 00:00:00 2001 From: quanpython Date: Tue, 25 Jul 2023 17:37:37 +0700 Subject: [PATCH 03/29] join WorkspacesShareUser in search workspaces API --- studio/app/common/routers/workspace.py | 28 +++++++++++++++++--------- studio/app/common/schemas/workspace.py | 1 + 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/studio/app/common/routers/workspace.py b/studio/app/common/routers/workspace.py index a49cd37ff..d96783ceb 100644 --- a/studio/app/common/routers/workspace.py +++ b/studio/app/common/routers/workspace.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, Depends, HTTPException from fastapi_pagination import LimitOffsetPage from fastapi_pagination.ext.sqlmodel import paginate -from sqlmodel import Session, select +from sqlmodel import Session, or_, select from studio.app.common import models as common_model from studio.app.common.core.auth.auth_dependencies import get_current_user @@ -33,20 +33,30 @@ def search_workspaces( current_user: User = Depends(get_current_user), ): sort_column = getattr(common_model.Workspace, sortOptions.sort[0] or "id") - return paginate( - session=db, - query=select(common_model.Workspace) + query = ( + select(common_model.Workspace) + .outerjoin( + common_model.WorkspacesShareUser, + common_model.Workspace.id == common_model.WorkspacesShareUser.workspace_id, + ) .filter( - common_model.Workspace.user_id == current_user.id, common_model.Workspace.deleted.is_(False), + or_( + common_model.WorkspacesShareUser.user_id == current_user.id, + common_model.Workspace.user_id == current_user.id, + ), ) .group_by(common_model.Workspace.id) .order_by( sort_column.desc() if sortOptions.sort[1] == SortDirection.desc else sort_column.asc() - ), + ) ) + data = paginate(db, query) + for ws in data.items: + ws.__dict__["user"] = db.query(common_model.User).get(ws.user_id) + return data @router.get( @@ -251,9 +261,9 @@ def update_workspace_share_status( .filter(common_model.WorkspacesShareUser.workspace_id == id) .delete(synchronize_session=False) ) - [ - db.add(common_model.WorkspacesShareUser(workspace_id=id, user_id=user_id)) + db.bulk_save_objects( + common_model.WorkspacesShareUser(workspace_id=id, user_id=user_id) for user_id in data.user_ids - ] + ) db.commit() return True diff --git a/studio/app/common/schemas/workspace.py b/studio/app/common/schemas/workspace.py index a3df2daba..574cb5ba7 100644 --- a/studio/app/common/schemas/workspace.py +++ b/studio/app/common/schemas/workspace.py @@ -9,6 +9,7 @@ class Workspace(BaseModel): id: Optional[int] name: str + user_id: Optional[int] user: Optional[UserInfo] created_at: Optional[datetime] updated_at: Optional[datetime] From df3a655e52ac8d97c12be8dcad65b0d20f0e5728 Mon Sep 17 00:00:00 2001 From: SangLv Date: Wed, 26 Jul 2023 10:15:24 +0700 Subject: [PATCH 04/29] fix user --- frontend/src/pages/Workspace/index.tsx | 38 ++++++++++++++------------ 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 63d7b62e1..c7195e237 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -33,6 +33,7 @@ type PopupType = { const columns = ( handleOpenPopupShare: () => void, handleOpenPopupDel: (id: number) => void, + userId?: {id: number} ) => ( [ { @@ -48,29 +49,32 @@ const columns = ( headerName: 'Workspace Name', minWidth: 200, editable: true, - renderCell: (params: GridRenderCellParams) => ( - - {params.value} - {params.row.owner !== "User 2" ? : ""} - - ), + renderCell: (params: GridRenderCellParams) => { + console.log(params) + return ( + + {params.value} + {params.row.owner !== "User 2" ? : ""} + + ) + }, }, { field: 'user', headerName: 'Owner', minWidth: 200, - renderCell: (params: GridRenderCellParams) => ( + renderCell: (params: GridRenderCellParams<{name: string, id: number}>) => ( - {params.value} - {params.value === "User 2" ? : ""} + {params.value?.name} + {params.value.id === userId?.id ? : ""} ), }, @@ -419,8 +423,8 @@ const Workspaces = () => { autoHeight rows={data?.items} editMode="row" - columns={columns(handleOpenPopupShare, handleOpenPopupDel) as any} - isCellEditable={(params) => /*params.row.user?.id === user?.id*/ !!params } + columns={columns(handleOpenPopupShare, handleOpenPopupDel, user) as any} + isCellEditable={(params) => params.row.user?.id === user?.id} processRowUpdate={processRowUpdate} /> From ef89905994e174c2f6b434d241aed6b3c2066aec Mon Sep 17 00:00:00 2001 From: SangLv Date: Wed, 26 Jul 2023 15:16:43 +0700 Subject: [PATCH 05/29] add pagination --- frontend/src/api/Workspace/index.ts | 14 +- frontend/src/pages/Workspace/index.tsx | 133 +++++++++++++----- .../store/slice/Workspace/WorkspaceType.ts | 2 +- .../slice/Workspace/WorkspacesActions.ts | 43 ++++-- 4 files changed, 139 insertions(+), 53 deletions(-) diff --git a/frontend/src/api/Workspace/index.ts b/frontend/src/api/Workspace/index.ts index e7a77a345..e1a5fc1af 100644 --- a/frontend/src/api/Workspace/index.ts +++ b/frontend/src/api/Workspace/index.ts @@ -1,12 +1,15 @@ +import { WorkspaceParams } from "store/slice/Workspace/WorkspaceType" import axios from "utils/axios" +import qs from 'qs' export type Data = { name?: string id?: number } -export const getWorkspacesApi = async () => { - const response = await axios.get(`/workspaces`) +export const getWorkspacesApi = async (params: { [key: string]: number}) => { + const paramsNew = qs.stringify(params, { indices: false }) + const response = await axios.get(`/workspaces?${paramsNew}`) return response.data } @@ -23,4 +26,9 @@ export const postWorkspaceApi = async (data: Data) => { export const putWorkspaceApi = async (data: Data) => { const response = await axios.put(`/workspace/${data.id}`, {name: data.name}) return response.data -} \ No newline at end of file +} + +export const importWorkspaceApi = async (data: Object) => { + const response = await axios.post(`/workspace/import`, {todo_dummy: data}) + return response.data +} diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index c7195e237..67c9f1eae 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -1,12 +1,12 @@ import { useSelector, useDispatch } from 'react-redux' -import { Box, styled, Button, Dialog, DialogTitle, DialogContent, DialogActions, Input } from '@mui/material' +import { Box, styled, Button, Dialog, DialogTitle, DialogContent, DialogActions, Input, Pagination } from '@mui/material' import { GridRenderCellParams, GridRowParams, DataGrid } from '@mui/x-data-grid' -import { DataGridPro, GridRowModesModel } from '@mui/x-data-grid-pro' -import { Link } from 'react-router-dom' +import { DataGridPro } from '@mui/x-data-grid-pro' +import { Link, useSearchParams } from 'react-router-dom' import { selectCurrentUser } from 'store/slice/User/UserSelector' import Loading from '../../components/common/Loading' import { @@ -14,10 +14,10 @@ import { } from 'store/slice/Workspace/WorkspaceSelector' import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt'; import CancelIcon from '@mui/icons-material/Cancel'; -import { ChangeEvent, useEffect, useState } from "react"; +import {ChangeEvent, useCallback, useEffect, useMemo, useState} from "react"; import EditIcon from '@mui/icons-material/Edit'; import PeopleOutlineIcon from '@mui/icons-material/PeopleOutline'; -import { delWorkspace, getWorkspaceList, postWorkspace, putWorkspace } from 'store/slice/Workspace/WorkspacesActions' +import { delWorkspace, getWorkspaceList, importWorkspace, postWorkspace, putWorkspace } from 'store/slice/Workspace/WorkspacesActions' type PopupType = { open: boolean @@ -33,7 +33,7 @@ type PopupType = { const columns = ( handleOpenPopupShare: () => void, handleOpenPopupDel: (id: number) => void, - userId?: {id: number} + user?: {id: number} ) => ( [ { @@ -50,18 +50,25 @@ const columns = ( minWidth: 200, editable: true, renderCell: (params: GridRenderCellParams) => { - console.log(params) return ( - - {params.value} - {params.row.owner !== "User 2" ? : ""} - + + + {params.row.name} + + {params.row.user_id === user?.id ? : ""} + ) }, }, @@ -74,7 +81,7 @@ const columns = ( sx={{display: "flex", alignItems: "center", gap: 2}} > {params.value?.name} - {params.value.id === userId?.id ? : ""} + {params.value.id !== user?.id ? : ""} ), }, @@ -101,9 +108,9 @@ const columns = ( headerName: '', minWidth: 130, renderCell: (params: GridRenderCellParams) => ( - - Result - + + Result + ), }, { @@ -121,21 +128,21 @@ const columns = ( headerName: '', minWidth: 90, renderCell: (params: GridRenderCellParams) => ( - params.row.owner !== "User 2" ? + params.row?.user_id === user?.id ? - + : "" - ), + ) }, { field: 'delete', headerName: '', minWidth: 130, renderCell: (params: GridRenderCellParams) => ( - params.row.owner !== "User 2" ? - handleOpenPopupDel(params.row.id)}> - Del - : "" + params.row?.user_id === user?.id ? + handleOpenPopupDel(params.row.id)}> + Del + : "" ), }, ] @@ -261,7 +268,7 @@ const PopupNew = ({ handleOkNew, error }: PopupType) => { - if(!setNewWorkSpace) return <> + if(!setNewWorkSpace) return null const handleName = (event: ChangeEvent) => { setNewWorkSpace(event.target.value) } @@ -339,11 +346,23 @@ const Workspaces = () => { const [newWorkspace, setNewWorkSpace] = useState() const [dataEdit, setDataEdit] = useState<{name?: string, id?: number}>() const [error, setError] = useState("") + const [searchParams, setParams] = useSearchParams() + + const offset = searchParams.get('offset') + const limit = searchParams.get('limit') + + const dataParams = useMemo(() => { + return { + offset: Number(offset) || 0, + limit: Number(limit) || 50, + } + //eslint-disable-next-line + }, [offset, limit]) useEffect(() => { - dispatch(getWorkspaceList()) + dispatch(getWorkspaceList(dataParams)) //eslint-disable-next-line - }, []) + }, [dataParams]) const handleOpenPopupShare = () => { setOpen({...open, share: true}) @@ -360,7 +379,7 @@ const Workspaces = () => { const handleOkDel = async () => { if(!idDel) return - await dispatch(delWorkspace(idDel)) + await dispatch(delWorkspace({id: idDel, params: dataParams})) setOpen({...open, del: false}) } @@ -382,7 +401,7 @@ const Workspaces = () => { const handleOkSave = async () => { if(!dataEdit) return - await dispatch(putWorkspace({name: dataEdit.name, id: dataEdit.id})) + await dispatch(putWorkspace({name: dataEdit.name, id: dataEdit.id, params: dataParams})) setOpen({...open, save: false}) } @@ -391,7 +410,7 @@ const Workspaces = () => { setError("is not empty") return } - await dispatch(postWorkspace({name: newWorkspace})) + await dispatch(postWorkspace({name: newWorkspace, params: dataParams})) setOpen({...open, new: false}) } @@ -405,6 +424,23 @@ const Workspaces = () => { return newRow; }; + const handleFileUpload = async (event: ChangeEvent) => { + dispatch(importWorkspace({})) + } + + const pagi = useCallback( + (page?: number) => { + return `limit=${data.limit}&offset=${ + page ? page - 1 : data.offset + }` + }, + [data.limit, data.offset], + ) + + const handlePage = (e: ChangeEvent, page: number) => { + setParams(`&${pagi(page)}`) + } + return ( Workspaces @@ -416,16 +452,41 @@ const Workspaces = () => { marginBottom: 2 }} > - Import + New params.row.user?.id === user?.id} processRowUpdate={processRowUpdate} + hideFooter={true} + /> + diff --git a/frontend/src/store/slice/Workspace/WorkspaceType.ts b/frontend/src/store/slice/Workspace/WorkspaceType.ts index 469bb64f9..a34877bc5 100644 --- a/frontend/src/store/slice/Workspace/WorkspaceType.ts +++ b/frontend/src/store/slice/Workspace/WorkspaceType.ts @@ -37,4 +37,4 @@ export type WorkspaceType = { // TODO: add fields required for workspace } -export type WorkspaceParams = { [key: string]: string | undefined | number | string[] } +export type WorkspaceParams = { [key: string]: string | undefined | number | string[] | object } diff --git a/frontend/src/store/slice/Workspace/WorkspacesActions.ts b/frontend/src/store/slice/Workspace/WorkspacesActions.ts index 7a6130435..741ac25ad 100644 --- a/frontend/src/store/slice/Workspace/WorkspacesActions.ts +++ b/frontend/src/store/slice/Workspace/WorkspacesActions.ts @@ -1,12 +1,15 @@ import { createAsyncThunk } from "@reduxjs/toolkit" -import {delWorkspaceApi, getWorkspacesApi, postWorkspaceApi, putWorkspaceApi } from "api/Workspace" -import {ItemsWorkspace, WORKSPACE_SLICE_NAME } from "./WorkspaceType" +import {delWorkspaceApi, getWorkspacesApi, importWorkspaceApi, postWorkspaceApi, putWorkspaceApi } from "api/Workspace" +import {ItemsWorkspace, WorkspaceDataDTO, WorkspaceParams, WORKSPACE_SLICE_NAME } from "./WorkspaceType" // @ts-ignore -export const getWorkspaceList = createAsyncThunk(`${WORKSPACE_SLICE_NAME}/getWorkspaceList`, async () => { +export const getWorkspaceList = createAsyncThunk< + WorkspaceDataDTO, + { [key: string]: number } +>(`${WORKSPACE_SLICE_NAME}/getWorkspaceList`, async (params, thunkAPI) => { // const { rejectWithValue } = thunkAPI try { - const response = await getWorkspacesApi() + const response = await getWorkspacesApi(params as { [key: string]: number }) return response } catch (e) { return null @@ -15,12 +18,12 @@ export const getWorkspaceList = createAsyncThunk(`${WORKSPACE_SLICE_NAME}/getWor export const delWorkspace = createAsyncThunk< boolean, - number ->(`${WORKSPACE_SLICE_NAME}/delWorkspaceList`, async (id, thunkAPI) => { + WorkspaceParams +>(`${WORKSPACE_SLICE_NAME}/delWorkspaceList`, async (data, thunkAPI) => { const { rejectWithValue,dispatch } = thunkAPI try { - const response = await delWorkspaceApi(id) - await dispatch(getWorkspaceList()) + const response = await delWorkspaceApi(Number(data.id)) + await dispatch(getWorkspaceList(data.params as { [key: string]: number })) return response } catch (e) { return rejectWithValue(e) @@ -29,12 +32,12 @@ export const delWorkspace = createAsyncThunk< export const postWorkspace = createAsyncThunk< ItemsWorkspace, - { name: string } + { name: string, params: { [key: string]: number }} >(`${WORKSPACE_SLICE_NAME}/postWorkspaceList`, async (data, thunkAPI) => { const { rejectWithValue, dispatch } = thunkAPI try { const response = await postWorkspaceApi(data) - await dispatch(getWorkspaceList()) + await dispatch(getWorkspaceList(data.params)) return response } catch (e) { return rejectWithValue(e) @@ -43,14 +46,28 @@ export const postWorkspace = createAsyncThunk< export const putWorkspace = createAsyncThunk< ItemsWorkspace, - { name?: string, id?: number } + { name?: string, id?: number, params: { [key: string]: number } } >(`${WORKSPACE_SLICE_NAME}/putWorkspaceList`, async (data, thunkAPI) => { const { rejectWithValue, dispatch } = thunkAPI try { const response = await putWorkspaceApi(data) - await dispatch(getWorkspaceList()) + await dispatch(getWorkspaceList(data.params)) return response } catch (e) { return rejectWithValue(e) } -}) \ No newline at end of file +}) + +export const importWorkspace = createAsyncThunk< + ItemsWorkspace, + { [key: string]: number } +>(`${WORKSPACE_SLICE_NAME}/importWorkspaceList`, async (data, thunkAPI) => { + const { rejectWithValue, dispatch } = thunkAPI + try { + const response = await importWorkspaceApi(data) + await dispatch(getWorkspaceList(data)) + return response + } catch (e) { + return rejectWithValue(e) + } +}) From b4a7c571593c72f5f90d1f1c35eccd78c70747e6 Mon Sep 17 00:00:00 2001 From: SangLv Date: Wed, 26 Jul 2023 15:30:26 +0700 Subject: [PATCH 06/29] fix filter database --- .../src/components/Database/DatabaseCells.tsx | 61 ++++++++++++------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/Database/DatabaseCells.tsx b/frontend/src/components/Database/DatabaseCells.tsx index b5ee7a10a..cb3937056 100644 --- a/frontend/src/components/Database/DatabaseCells.tsx +++ b/frontend/src/components/Database/DatabaseCells.tsx @@ -124,9 +124,9 @@ const DatabaseCells = ({ user }: CellProps) => { const dataParamsFilter = useMemo( () => ({ - brain_area: searchParams.get('brain_area') || '', - cre_driver: searchParams.get('cre_driver') || '', - reporter_line: searchParams.get('reporter_line') || '', + brain_area: searchParams.get('brain_area') || undefined, + cre_driver: searchParams.get('cre_driver') || undefined, + reporter_line: searchParams.get('reporter_line') || undefined, imaging_depth: Number(searchParams.get('imaging_depth')) || undefined, }), [searchParams], @@ -134,13 +134,13 @@ const DatabaseCells = ({ user }: CellProps) => { const fetchApi = () => { const api = !user ? getCellsPublicDatabase : getCellsDatabase - dispatch(api(dataParams)) + dispatch(api({...dataParamsFilter, ...dataParams})) } useEffect(() => { fetchApi() //eslint-disable-next-line - }, [dataParams, user]) + }, [dataParams, user, dataParamsFilter]) const handleOpenDialog = (data: string[]) => { setDataDialog({ type: 'image', data }) @@ -180,31 +180,48 @@ const DatabaseCells = ({ user }: CellProps) => { [pagiFilter, getParamsData], ) + // const handleFilter = ( + // model: GridFilterModel | any, + // details: GridCallbackDetails, + // ) => { + // let filter: string + // if (!!model.items[0]?.value) { + // filter = model.items + // .filter((item: { [key: string]: string }) => item.value) + // .map((item: any) => { + // return `${item.field}=${item?.value}` + // }) + // .join('&') + // } else { + // filter = '' + // } + // if (!model.items[0]) { + // setParams( + // `${filter}&sort=${dataParams.sort[0]}&sort=${dataParams.sort[1]}&${pagiFilter}`, + // ) + // return + // } + // setParams( + // `${filter}&sort=${dataParams.sort[0]}&sort=${dataParams.sort[1]}&${pagiFilter}`, + // ) + // } const handleFilter = ( - model: GridFilterModel | any, - details: GridCallbackDetails, + model: GridFilterModel | any, + details: GridCallbackDetails, ) => { let filter: string if (!!model.items[0]?.value) { - //todo multiple filter with version pro. Issue task #55 filter = model.items - .filter((item: { [key: string]: string }) => item.value) - .map((item: any) => { - return `${item.field}=${item?.value}` - }) - .join('&') + .filter((item: { [key: string]: string }) => item.value) + .map((item: any) => { + return `${item.field}=${item?.value}` + }) + .join('&') } else { filter = '' } - if (!model.items[0]) { - setParams( - `${filter}&sort=${dataParams.sort[0]}&sort=${dataParams.sort[1]}&${pagiFilter}`, - ) - return - } - setParams( - `${filter}&sort=${dataParams.sort[0]}&sort=${dataParams.sort[1]}&${pagiFilter}`, - ) + const {sort} = dataParams + setParams(`${filter}&sort=${sort[0] || ''}&sort=${sort[1] || ''}&${pagiFilter}`) } const getColumns = useMemo(() => { From b884ba1bca82323f1945894a5eb03d338a166056 Mon Sep 17 00:00:00 2001 From: SangLv Date: Wed, 26 Jul 2023 17:25:10 +0700 Subject: [PATCH 07/29] add api download --- frontend/src/api/Workspace/index.ts | 5 +++++ frontend/src/pages/Workspace/index.tsx | 11 ++++++++--- .../store/slice/Workspace/WorkspacesActions.ts | 15 ++++++++++++++- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/frontend/src/api/Workspace/index.ts b/frontend/src/api/Workspace/index.ts index e1a5fc1af..63b4aefea 100644 --- a/frontend/src/api/Workspace/index.ts +++ b/frontend/src/api/Workspace/index.ts @@ -32,3 +32,8 @@ export const importWorkspaceApi = async (data: Object) => { const response = await axios.post(`/workspace/import`, {todo_dummy: data}) return response.data } + +export const exportWorkspaceApi = async (id: number) => { + const response = await axios.get(`/workspace/export/${id}`) + return response.data +} diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 67c9f1eae..22fb34383 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -17,7 +17,7 @@ import CancelIcon from '@mui/icons-material/Cancel'; import {ChangeEvent, useCallback, useEffect, useMemo, useState} from "react"; import EditIcon from '@mui/icons-material/Edit'; import PeopleOutlineIcon from '@mui/icons-material/PeopleOutline'; -import { delWorkspace, getWorkspaceList, importWorkspace, postWorkspace, putWorkspace } from 'store/slice/Workspace/WorkspacesActions' +import { delWorkspace, exportWorkspace, getWorkspaceList, importWorkspace, postWorkspace, putWorkspace } from 'store/slice/Workspace/WorkspacesActions' type PopupType = { open: boolean @@ -33,6 +33,7 @@ type PopupType = { const columns = ( handleOpenPopupShare: () => void, handleOpenPopupDel: (id: number) => void, + handleDownload: (id: number) => void, user?: {id: number} ) => ( [ @@ -118,7 +119,7 @@ const columns = ( headerName: '', minWidth: 90, renderCell: (params: GridRenderCellParams) => ( - + handleDownload(params?.row?.id)}> ), @@ -441,6 +442,10 @@ const Workspaces = () => { setParams(`&${pagi(page)}`) } + const handleDownload = async (id: number) => { + dispatch(exportWorkspace(id)) + } + return ( Workspaces @@ -477,7 +482,7 @@ const Workspaces = () => { params.row.user?.id === user?.id} processRowUpdate={processRowUpdate} hideFooter={true} diff --git a/frontend/src/store/slice/Workspace/WorkspacesActions.ts b/frontend/src/store/slice/Workspace/WorkspacesActions.ts index 741ac25ad..b3c37f3d2 100644 --- a/frontend/src/store/slice/Workspace/WorkspacesActions.ts +++ b/frontend/src/store/slice/Workspace/WorkspacesActions.ts @@ -1,5 +1,5 @@ import { createAsyncThunk } from "@reduxjs/toolkit" -import {delWorkspaceApi, getWorkspacesApi, importWorkspaceApi, postWorkspaceApi, putWorkspaceApi } from "api/Workspace" +import {delWorkspaceApi, exportWorkspaceApi, getWorkspacesApi, importWorkspaceApi, postWorkspaceApi, putWorkspaceApi } from "api/Workspace" import {ItemsWorkspace, WorkspaceDataDTO, WorkspaceParams, WORKSPACE_SLICE_NAME } from "./WorkspaceType" // @ts-ignore @@ -71,3 +71,16 @@ export const importWorkspace = createAsyncThunk< return rejectWithValue(e) } }) + +export const exportWorkspace = createAsyncThunk< + ItemsWorkspace, + number +>(`${WORKSPACE_SLICE_NAME}/exportWorkspaceList`, async (id, thunkAPI) => { + const { rejectWithValue, dispatch } = thunkAPI + try { + const response = await exportWorkspaceApi(id) + return response + } catch (e) { + return rejectWithValue(e) + } +}) From c1eff691f39089732c32870262ca08cabba5d2b2 Mon Sep 17 00:00:00 2001 From: SangLv Date: Thu, 27 Jul 2023 12:18:38 +0700 Subject: [PATCH 08/29] fix alert and add moment fix format create at --- frontend/package.json | 1 + frontend/src/pages/Workspace/index.tsx | 30 +++++++++++++++++--------- frontend/yarn.lock | 5 +++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 46c4486b9..632713dbb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "axios": "^0.21.1", "colormap": "^2.3.2", "flexlayout-react": "^0.5.12", + "moment": "^2.29.4", "notistack": "^2.0.3", "plotly.js": "^2.6.0", "qs": "^6.11.2", diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 22fb34383..c3c1d611f 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -18,6 +18,7 @@ import {ChangeEvent, useCallback, useEffect, useMemo, useState} from "react"; import EditIcon from '@mui/icons-material/Edit'; import PeopleOutlineIcon from '@mui/icons-material/PeopleOutline'; import { delWorkspace, exportWorkspace, getWorkspaceList, importWorkspace, postWorkspace, putWorkspace } from 'store/slice/Workspace/WorkspacesActions' +import moment from 'moment' type PopupType = { open: boolean @@ -91,7 +92,7 @@ const columns = ( headerName: 'Created', minWidth: 200, renderCell: (params: GridRenderCellParams) => ( - {params.value} + {moment(params.value).format("YYYY/MM/DD HH:MM")} ), }, { @@ -394,6 +395,7 @@ const Workspaces = () => { const handleClosePopupNew = () => { setOpen({...open, new: false}) + setError("") } const handleClosePopupSave = () => { @@ -413,11 +415,13 @@ const Workspaces = () => { } await dispatch(postWorkspace({name: newWorkspace, params: dataParams})) setOpen({...open, new: false}) + setError("") + setNewWorkSpace("") } const processRowUpdate = (newRow: any) => { if(!newRow?.name) { - alert("is not empty") + alert("Workspace Name cann't empty") return } setOpen({...open, save: true}) @@ -479,14 +483,20 @@ const Workspaces = () => { New - params.row.user?.id === user?.id} - processRowUpdate={processRowUpdate} - hideFooter={true} - /> + + params.row.user?.id === user?.id} + processRowUpdate={processRowUpdate} + hideFooter={true} + /> + Date: Thu, 27 Jul 2023 14:19:00 +0700 Subject: [PATCH 09/29] fix bug edit name --- frontend/src/pages/Workspace/index.tsx | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index c3c1d611f..1d0be7df0 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -3,9 +3,10 @@ import { Box, styled, Button, Dialog, DialogTitle, DialogContent, DialogActions, import { GridRenderCellParams, GridRowParams, - DataGrid + DataGrid, + GridEventListener } from '@mui/x-data-grid' -import { DataGridPro } from '@mui/x-data-grid-pro' +import { DataGridPro, GridRowModesModel } from '@mui/x-data-grid-pro' import { Link, useSearchParams } from 'react-router-dom' import { selectCurrentUser } from 'store/slice/User/UserSelector' import Loading from '../../components/common/Loading' @@ -348,6 +349,10 @@ const Workspaces = () => { const [newWorkspace, setNewWorkSpace] = useState() const [dataEdit, setDataEdit] = useState<{name?: string, id?: number}>() const [error, setError] = useState("") + + const [rows, setRows] = useState(data.items); + const [rowModesModel, setRowModesModel] = useState({}); + const [searchParams, setParams] = useSearchParams() const offset = searchParams.get('offset') @@ -422,13 +427,17 @@ const Workspaces = () => { const processRowUpdate = (newRow: any) => { if(!newRow?.name) { alert("Workspace Name cann't empty") - return + return {...newRow, name: '12312321'} } setOpen({...open, save: true}) setDataEdit({id: newRow?.id, name: newRow?.name}) return newRow; }; + const onProcessRowUpdateError = (newRow: any) => { + return newRow + }; + const handleFileUpload = async (event: ChangeEvent) => { dispatch(importWorkspace({})) } @@ -450,6 +459,10 @@ const Workspaces = () => { dispatch(exportWorkspace(id)) } + const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { + setRowModesModel(newRowModesModel); + }; + return ( Workspaces @@ -490,10 +503,11 @@ const Workspaces = () => { }}> params.row.user?.id === user?.id} processRowUpdate={processRowUpdate} + onProcessRowUpdateError={onProcessRowUpdateError} hideFooter={true} /> From 1e5cf5396c0cbf85682189fc72ad4eaebcc2906f Mon Sep 17 00:00:00 2001 From: SangLv Date: Thu, 27 Jul 2023 15:26:41 +0700 Subject: [PATCH 10/29] reset value name when name is empty --- .../src/components/Database/DatabaseCells.tsx | 25 ------------------- frontend/src/pages/Workspace/index.tsx | 5 ++-- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/Database/DatabaseCells.tsx b/frontend/src/components/Database/DatabaseCells.tsx index cb3937056..4f83c489b 100644 --- a/frontend/src/components/Database/DatabaseCells.tsx +++ b/frontend/src/components/Database/DatabaseCells.tsx @@ -180,31 +180,6 @@ const DatabaseCells = ({ user }: CellProps) => { [pagiFilter, getParamsData], ) - // const handleFilter = ( - // model: GridFilterModel | any, - // details: GridCallbackDetails, - // ) => { - // let filter: string - // if (!!model.items[0]?.value) { - // filter = model.items - // .filter((item: { [key: string]: string }) => item.value) - // .map((item: any) => { - // return `${item.field}=${item?.value}` - // }) - // .join('&') - // } else { - // filter = '' - // } - // if (!model.items[0]) { - // setParams( - // `${filter}&sort=${dataParams.sort[0]}&sort=${dataParams.sort[1]}&${pagiFilter}`, - // ) - // return - // } - // setParams( - // `${filter}&sort=${dataParams.sort[0]}&sort=${dataParams.sort[1]}&${pagiFilter}`, - // ) - // } const handleFilter = ( model: GridFilterModel | any, details: GridCallbackDetails, diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 1d0be7df0..03f26d118 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -349,6 +349,7 @@ const Workspaces = () => { const [newWorkspace, setNewWorkSpace] = useState() const [dataEdit, setDataEdit] = useState<{name?: string, id?: number}>() const [error, setError] = useState("") + const [initNameCell, setInitNameCell] = useState() const [rows, setRows] = useState(data.items); const [rowModesModel, setRowModesModel] = useState({}); @@ -424,10 +425,10 @@ const Workspaces = () => { setNewWorkSpace("") } - const processRowUpdate = (newRow: any) => { + const processRowUpdate = (newRow: any, preValue: any) => { if(!newRow?.name) { alert("Workspace Name cann't empty") - return {...newRow, name: '12312321'} + return {...newRow, name: preValue.name} } setOpen({...open, save: true}) setDataEdit({id: newRow?.id, name: newRow?.name}) From 2a4096c89cd5f4a42b4297469e8667ecdb5514a2 Mon Sep 17 00:00:00 2001 From: SangLv Date: Thu, 27 Jul 2023 18:06:48 +0700 Subject: [PATCH 11/29] fix bug --- frontend/src/api/Workspace/index.ts | 1 - frontend/src/pages/Workspace/index.tsx | 57 ++++++++++++------- .../slice/Workspace/WorkspacesActions.ts | 2 +- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/frontend/src/api/Workspace/index.ts b/frontend/src/api/Workspace/index.ts index 63b4aefea..e4ca5d8bf 100644 --- a/frontend/src/api/Workspace/index.ts +++ b/frontend/src/api/Workspace/index.ts @@ -1,4 +1,3 @@ -import { WorkspaceParams } from "store/slice/Workspace/WorkspaceType" import axios from "utils/axios" import qs from 'qs' diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 03f26d118..9a059dccf 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -4,9 +4,8 @@ import { GridRenderCellParams, GridRowParams, DataGrid, - GridEventListener } from '@mui/x-data-grid' -import { DataGridPro, GridRowModesModel } from '@mui/x-data-grid-pro' +import { DataGridPro, GridRowEditStopReasons, GridEventListener, GridRowModesModel, GridRowModel} from '@mui/x-data-grid-pro' import { Link, useSearchParams } from 'react-router-dom' import { selectCurrentUser } from 'store/slice/User/UserSelector' import Loading from '../../components/common/Loading' @@ -347,13 +346,9 @@ const Workspaces = () => { const [open, setOpen] = useState({share: false, del: false, new: false, save: false}) const [idDel, setIdDel] = useState() const [newWorkspace, setNewWorkSpace] = useState() - const [dataEdit, setDataEdit] = useState<{name?: string, id?: number}>() const [error, setError] = useState("") - const [initNameCell, setInitNameCell] = useState() - - const [rows, setRows] = useState(data.items); + const [initName, setInitName] = useState("") const [rowModesModel, setRowModesModel] = useState({}); - const [searchParams, setParams] = useSearchParams() const offset = searchParams.get('offset') @@ -405,13 +400,18 @@ const Workspaces = () => { } const handleClosePopupSave = () => { + setRowModesModel({ + ...rowModesModel, + [Object.keys(rowModesModel)[0]]: { mode: 'view', ignoreModifications: true } as any, + }); setOpen({...open, save: false}) } const handleOkSave = async () => { - if(!dataEdit) return - await dispatch(putWorkspace({name: dataEdit.name, id: dataEdit.id, params: dataParams})) - setOpen({...open, save: false}) + setRowModesModel({ + ...rowModesModel, + [Object.keys(rowModesModel)[0]]: { mode: 'view' } as any, + }); } const handleOkNew = async () => { @@ -425,16 +425,6 @@ const Workspaces = () => { setNewWorkSpace("") } - const processRowUpdate = (newRow: any, preValue: any) => { - if(!newRow?.name) { - alert("Workspace Name cann't empty") - return {...newRow, name: preValue.name} - } - setOpen({...open, save: true}) - setDataEdit({id: newRow?.id, name: newRow?.name}) - return newRow; - }; - const onProcessRowUpdateError = (newRow: any) => { return newRow }; @@ -464,6 +454,26 @@ const Workspaces = () => { setRowModesModel(newRowModesModel); }; + const onRowEditStop: GridEventListener<"rowEditStop"> = (params, event) => { + setInitName(params.row.name) + if (params.reason === GridRowEditStopReasons.rowFocusOut) { + event.defaultMuiPrevented = true; + } + //todo pending save state row edit + setOpen({...open, save: true}) + }; + + const processRowUpdate = async (newRow: GridRowModel) => { + if(!newRow.name) { + alert("Workspace Name cann't empty") + setOpen({...open, save: false}) + return { ...newRow, name: initName} + } + await dispatch(putWorkspace({name: newRow.name, id: Number(Object.keys(rowModesModel)[0]), params: dataParams})) + setOpen({...open, save: false}) + return newRow + } + return ( Workspaces @@ -504,11 +514,14 @@ const Workspaces = () => { }}> params.row.user?.id === user?.id} - processRowUpdate={processRowUpdate} onProcessRowUpdateError={onProcessRowUpdateError} + onRowEditStop={onRowEditStop} + processRowUpdate={processRowUpdate as any} hideFooter={true} /> diff --git a/frontend/src/store/slice/Workspace/WorkspacesActions.ts b/frontend/src/store/slice/Workspace/WorkspacesActions.ts index b3c37f3d2..171243bf3 100644 --- a/frontend/src/store/slice/Workspace/WorkspacesActions.ts +++ b/frontend/src/store/slice/Workspace/WorkspacesActions.ts @@ -76,7 +76,7 @@ export const exportWorkspace = createAsyncThunk< ItemsWorkspace, number >(`${WORKSPACE_SLICE_NAME}/exportWorkspaceList`, async (id, thunkAPI) => { - const { rejectWithValue, dispatch } = thunkAPI + const { rejectWithValue } = thunkAPI try { const response = await exportWorkspaceApi(id) return response From 594326abaa70ea67fb7b08c789a571307314e2ca Mon Sep 17 00:00:00 2001 From: SangLv Date: Thu, 27 Jul 2023 18:59:49 +0700 Subject: [PATCH 12/29] fix format date --- frontend/src/pages/Workspace/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 9a059dccf..bbec36206 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -92,7 +92,7 @@ const columns = ( headerName: 'Created', minWidth: 200, renderCell: (params: GridRenderCellParams) => ( - {moment(params.value).format("YYYY/MM/DD HH:MM")} + {moment(params.value).format("YYYY/MM/DD hh:mm")} ), }, { @@ -303,6 +303,7 @@ const PopupNew = ({ } const PopupDelete = ({open, handleClose, handleOkDel}: PopupType) => { + if(!handleOkDel) return null return ( Date: Fri, 28 Jul 2023 11:48:33 +0900 Subject: [PATCH 13/29] fix auth actions --- frontend/src/components/Layout/index.tsx | 16 +++++++--------- frontend/src/pages/Login/index.tsx | 8 ++------ frontend/src/pages/ResetPassword/index.tsx | 1 + 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/Layout/index.tsx b/frontend/src/components/Layout/index.tsx index 3ce8e896e..91781994a 100644 --- a/frontend/src/components/Layout/index.tsx +++ b/frontend/src/components/Layout/index.tsx @@ -12,7 +12,6 @@ import { IS_STANDALONE } from 'const/Mode' import Loading from 'components/common/Loading' const authRequiredPathRegex = /^\/console\/?.*/ -const redirectAfterLoginPaths = ['/login', '/reset-password', '/console'] const Layout = ({ children }: { children?: ReactNode }) => { const user = useSelector(selectCurrentUser) @@ -20,11 +19,11 @@ const Layout = ({ children }: { children?: ReactNode }) => { const navigate = useNavigate() const dispatch = useDispatch() - const [loading, setLoadingAuth] = useState(!IS_STANDALONE && authRequiredPathRegex.test(window.location.pathname)) + const [loading, setLoadingAuth] = useState(!IS_STANDALONE && authRequiredPathRegex.test(location.pathname)) useEffect(() => { !IS_STANDALONE && - authRequiredPathRegex.test(window.location.pathname) && + authRequiredPathRegex.test(location.pathname) && checkAuth() // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.pathname, user]) @@ -35,18 +34,17 @@ const Layout = ({ children }: { children?: ReactNode }) => { return } const token = getToken() - const willRedirect = redirectAfterLoginPaths.includes( - window.location.pathname, - ) + const isLogin = location.pathname === '/login' + try { if (token) { await dispatch(getMe()) - if (willRedirect) navigate('/console') + if (isLogin) navigate('/console') return - } else if (!willRedirect) throw new Error('fail auth') + } else if (!isLogin) throw new Error('fail auth') } catch { - navigate('/login') + navigate('/login', { replace: true }) } finally { if(loading) setLoadingAuth(false) } diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx index 2431f4577..9f87c6416 100644 --- a/frontend/src/pages/Login/index.tsx +++ b/frontend/src/pages/Login/index.tsx @@ -1,6 +1,6 @@ import { Box, Stack, styled, Typography } from '@mui/material' import { useDispatch } from 'react-redux' -import { getMe, login } from 'store/slice/User/UserActions' +import { login } from 'store/slice/User/UserActions' import { AppDispatch } from 'store/store' import { ChangeEvent, FormEvent, useState } from 'react' import { Link, useNavigate } from 'react-router-dom' @@ -28,11 +28,7 @@ const Login = () => { dispatch(login(values)) .unwrap() .then((_) => { - dispatch(getMe()) - .unwrap() - .then((_) => { - navigate('/console') - }) + navigate('/console') }) .catch((_) => { setErrors({ email: 'Email or password is wrong', password: '' }) diff --git a/frontend/src/pages/ResetPassword/index.tsx b/frontend/src/pages/ResetPassword/index.tsx index dc8b1918f..062b6fa32 100644 --- a/frontend/src/pages/ResetPassword/index.tsx +++ b/frontend/src/pages/ResetPassword/index.tsx @@ -25,6 +25,7 @@ const ResetPassword = () => { await sendResetPasswordMailApi(values.email) setTimeout(()=>{ alert(` You'll receive a link to reset your password at ${values.email}. Please check your mail!`) + navigate('/login') },300) } catch { From 0184f5d168c10a0730573a171a8c5ff68d2716aa Mon Sep 17 00:00:00 2001 From: SangLv Date: Fri, 28 Jul 2023 11:49:54 +0700 Subject: [PATCH 14/29] Add edit row with single click in workspace Page, add isAnyOf in WorkspaceSlice --- frontend/src/api/Workspace/index.ts | 17 +- frontend/src/pages/Workspace/index.tsx | 662 +++++++++--------- .../slice/Workspace/WorkspaceSelector.ts | 4 +- .../store/slice/Workspace/WorkspaceSlice.ts | 88 ++- .../store/slice/Workspace/WorkspaceType.ts | 9 +- .../slice/Workspace/WorkspacesActions.ts | 125 ++-- 6 files changed, 458 insertions(+), 447 deletions(-) diff --git a/frontend/src/api/Workspace/index.ts b/frontend/src/api/Workspace/index.ts index e4ca5d8bf..2e494f5c8 100644 --- a/frontend/src/api/Workspace/index.ts +++ b/frontend/src/api/Workspace/index.ts @@ -1,12 +1,9 @@ -import axios from "utils/axios" +import axios from 'utils/axios' import qs from 'qs' -export type Data = { - name?: string - id?: number -} +export type DataRequestEdit = { name: string; id?: number } -export const getWorkspacesApi = async (params: { [key: string]: number}) => { +export const getWorkspacesApi = async (params: { [key: string]: number }) => { const paramsNew = qs.stringify(params, { indices: false }) const response = await axios.get(`/workspaces?${paramsNew}`) return response.data @@ -17,18 +14,18 @@ export const delWorkspaceApi = async (id: number) => { return response.data } -export const postWorkspaceApi = async (data: Data) => { +export const postWorkspaceApi = async (data: DataRequestEdit) => { const response = await axios.post(`/workspace`, data) return response.data } -export const putWorkspaceApi = async (data: Data) => { - const response = await axios.put(`/workspace/${data.id}`, {name: data.name}) +export const putWorkspaceApi = async (data: DataRequestEdit) => { + const response = await axios.put(`/workspace/${data.id}`, { name: data.name }) return response.data } export const importWorkspaceApi = async (data: Object) => { - const response = await axios.post(`/workspace/import`, {todo_dummy: data}) + const response = await axios.post(`/workspace/import`, { todo_dummy: data }) return response.data } diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index bbec36206..db1c45bdd 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -1,23 +1,45 @@ import { useSelector, useDispatch } from 'react-redux' -import { Box, styled, Button, Dialog, DialogTitle, DialogContent, DialogActions, Input, Pagination } from '@mui/material' import { - GridRenderCellParams, - GridRowParams, - DataGrid, -} from '@mui/x-data-grid' -import { DataGridPro, GridRowEditStopReasons, GridEventListener, GridRowModesModel, GridRowModel} from '@mui/x-data-grid-pro' + Box, + styled, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Input, + Pagination, + IconButton, +} from '@mui/material' +import { GridRenderCellParams, GridRowParams, DataGrid } from '@mui/x-data-grid' +import { + DataGridPro, + GridRowEditStopReasons, + GridEventListener, + GridRowModesModel, + GridRowModel, + GridRowModes, +} from '@mui/x-data-grid-pro' import { Link, useSearchParams } from 'react-router-dom' import { selectCurrentUser } from 'store/slice/User/UserSelector' import Loading from '../../components/common/Loading' import { - selectIsLoadingWorkspaceList, selectWorkspaceData, + selectIsLoadingWorkspaceList, + selectWorkspaceData, } from 'store/slice/Workspace/WorkspaceSelector' -import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt'; -import CancelIcon from '@mui/icons-material/Cancel'; -import {ChangeEvent, useCallback, useEffect, useMemo, useState} from "react"; -import EditIcon from '@mui/icons-material/Edit'; -import PeopleOutlineIcon from '@mui/icons-material/PeopleOutline'; -import { delWorkspace, exportWorkspace, getWorkspaceList, importWorkspace, postWorkspace, putWorkspace } from 'store/slice/Workspace/WorkspacesActions' +import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt' +import CancelIcon from '@mui/icons-material/Cancel' +import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react' +import EditIcon from '@mui/icons-material/Edit' +import PeopleOutlineIcon from '@mui/icons-material/PeopleOutline' +import { + delWorkspace, + exportWorkspace, + getWorkspaceList, + importWorkspace, + postWorkspace, + putWorkspace, +} from 'store/slice/Workspace/WorkspacesActions' import moment from 'moment' type PopupType = { @@ -32,205 +54,230 @@ type PopupType = { } const columns = ( - handleOpenPopupShare: () => void, - handleOpenPopupDel: (id: number) => void, - handleDownload: (id: number) => void, - user?: {id: number} - ) => ( - [ - { - field: 'id', - headerName: 'ID', - minWidth: 160, - renderCell: (params: GridRenderCellParams) => ( - {params.value} - ), - }, - { - field: 'name', - headerName: 'Workspace Name', - minWidth: 200, - editable: true, - renderCell: (params: GridRenderCellParams) => { - return ( - - - {params.row.name} - - {params.row.user_id === user?.id ? : ""} - - ) - }, - }, - { - field: 'user', - headerName: 'Owner', - minWidth: 200, - renderCell: (params: GridRenderCellParams<{name: string, id: number}>) => ( - void, + handleOpenPopupDel: (id: number) => void, + handleDownload: (id: number) => void, + user?: { id: number }, + onEdit?: (id: number) => void, +) => [ + { + field: 'id', + headerName: 'ID', + minWidth: 160, + filterable: false, // todo enable when finish api + sortable: false, // todo enable when finish api + renderCell: (params: GridRenderCellParams) => ( + {params.value} + ), + }, + { + field: 'name', + headerName: 'Workspace Name', + minWidth: 200, + editable: true, + filterable: false, // todo enable when finish api + sortable: false, // todo enable when finish api + renderCell: (params: GridRenderCellParams) => { + return ( + + - {params.value?.name} - {params.value.id !== user?.id ? : ""} - - ), - }, - { - field: 'created_at', - headerName: 'Created', - minWidth: 200, - renderCell: (params: GridRenderCellParams) => ( - {moment(params.value).format("YYYY/MM/DD hh:mm")} - ), - }, - { - field: 'workflow', - headerName: '', - minWidth: 160, - renderCell: (params: GridRenderCellParams) => ( - - Workflow - - ), - }, - { - field: 'result', - headerName: '', - minWidth: 130, - renderCell: (params: GridRenderCellParams) => ( - - Result - - ), - }, - { - field: 'download', - headerName: '', - minWidth: 90, - renderCell: (params: GridRenderCellParams) => ( - handleDownload(params?.row?.id)}> - - - ), - }, - { - field: 'share', - headerName: '', - minWidth: 90, - renderCell: (params: GridRenderCellParams) => ( - params.row?.user_id === user?.id ? - - - : "" - ) - }, - { - field: 'delete', - headerName: '', - minWidth: 130, - renderCell: (params: GridRenderCellParams) => ( - params.row?.user_id === user?.id ? - handleOpenPopupDel(params.row.id)}> - Del - : "" - ), - }, - ] -) - -const columnsShare = (handleShareFalse: (parmas: GridRenderCellParams) => void) => ( - [ - { - field: "name", - headerName: "Name", - minWidth: 140, - renderCell: (params: GridRenderCellParams) => ( - {params.row.name} - ), - }, - { - field: "lab", - headerName: "Lab", - minWidth: 280, - renderCell: (params: GridRenderCellParams) => ( - {params.row.email} - ), - }, - { - field: "email", - headerName: "Email", - minWidth: 280, - renderCell: (params: GridRenderCellParams) => ( - {params.row.email} - ), - }, - { - field: "share", - headerName: "", - minWidth: 130, - renderCell: (params: GridRenderCellParams) => { - if(!params.row.share) return "" - return ( - - ) - } - }, - ] -) + {params.row.name} + + {params.row.user_id === user?.id ? ( + onEdit?.(params.row.id)}> + + + ) : ( + '' + )} + + ) + }, + }, + { + field: 'user', + headerName: 'Owner', + filterable: false, // todo enable when finish api + sortable: false, // todo enable when finish api + minWidth: 200, + renderCell: ( + params: GridRenderCellParams<{ name: string; id: number }>, + ) => ( + + {params.value?.name} + {params.value.id !== user?.id ? : ''} + + ), + }, + { + field: 'created_at', + headerName: 'Created', + minWidth: 200, + filterable: false, // todo enable when finish api + sortable: false, // todo enable when finish api + renderCell: (params: GridRenderCellParams) => ( + {moment(params.value).format('YYYY/MM/DD hh:mm')} + ), + }, + { + field: 'workflow', + headerName: '', + minWidth: 160, + filterable: false, // todo enable when finish api + sortable: false, // todo enable when finish api + renderCell: (params: GridRenderCellParams) => ( + Workflow + ), + }, + { + field: 'result', + headerName: '', + minWidth: 130, + filterable: false, // todo enable when finish api + sortable: false, // todo enable when finish api + renderCell: (params: GridRenderCellParams) => ( + Result + ), + }, + { + field: 'download', + headerName: '', + minWidth: 90, + filterable: false, // todo enable when finish api + sortable: false, // todo enable when finish api + renderCell: (params: GridRenderCellParams) => ( + handleDownload(params?.row?.id)}> + + + ), + }, + { + field: 'share', + headerName: '', + minWidth: 90, + filterable: false, // todo enable when finish api + sortable: false, // todo enable when finish api + renderCell: (params: GridRenderCellParams) => + params.row?.user_id === user?.id ? ( + + + + ) : ( + '' + ), + }, + { + field: 'delete', + headerName: '', + minWidth: 130, + filterable: false, // todo enable when finish api + sortable: false, // todo enable when finish api + renderCell: (params: GridRenderCellParams) => + params.row?.user_id === user?.id ? ( + handleOpenPopupDel(params.row.id)}> + Del + + ) : ( + '' + ), + }, +] + +const columnsShare = ( + handleShareFalse: (parmas: GridRenderCellParams) => void, +) => [ + { + field: 'name', + headerName: 'Name', + minWidth: 140, + renderCell: (params: GridRenderCellParams) => ( + {params.row.name} + ), + }, + { + field: 'lab', + headerName: 'Lab', + minWidth: 280, + renderCell: (params: GridRenderCellParams) => ( + {params.row.email} + ), + }, + { + field: 'email', + headerName: 'Email', + minWidth: 280, + renderCell: (params: GridRenderCellParams) => ( + {params.row.email} + ), + }, + { + field: 'share', + headerName: '', + minWidth: 130, + renderCell: (params: GridRenderCellParams) => { + if (!params.row.share) return '' + return ( + + ) + }, + }, +] const dataShare = [ { id: 1, - name: "User 1", - lab: "Labxxxx", - email: "aaaaa@gmail.com", - share: false + name: 'User 1', + lab: 'Labxxxx', + email: 'aaaaa@gmail.com', + share: false, }, { id: 2, - name: "User 2", - lab: "Labxxxx", - email: "aaaaa@gmail.com", - share: true + name: 'User 2', + lab: 'Labxxxx', + email: 'aaaaa@gmail.com', + share: true, }, { id: 3, - name: "User 3", - lab: "Labxxxx", - email: "aaaaa@gmail.com", - share: true - } + name: 'User 3', + lab: 'Labxxxx', + email: 'aaaaa@gmail.com', + share: true, + }, ] -const PopupShare = ({open, handleClose}: PopupType) => { +const PopupShare = ({ open, handleClose }: PopupType) => { const [tableShare, setTableShare] = useState(dataShare) const handleShareTrue = (params: GridRowParams) => { - if(params.row.share) return - const index = tableShare.findIndex(item => item.id === params.id) - setTableShare(pre => { + if (params.row.share) return + const index = tableShare.findIndex((item) => item.id === params.id) + setTableShare((pre) => { pre[index].share = true return pre }) } const handleShareFalse = (params: GridRenderCellParams) => { - const indexSearch = tableShare.findIndex(item => item.id === params.id) + const indexSearch = tableShare.findIndex((item) => item.id === params.id) const newData = tableShare.map((item, index) => { - if(index === indexSearch) return {...item, share: false} + if (index === indexSearch) return { ...item, share: false } return item }) setTableShare(newData) @@ -238,16 +285,12 @@ const PopupShare = ({open, handleClose}: PopupType) => { return ( - + Share Workspace アクセス許可ユーザー { - if(!setNewWorkSpace) return null + if (!setNewWorkSpace) return null const handleName = (event: ChangeEvent) => { setNewWorkSpace(event.target.value) } return ( - + Create New Workspace - + -
- {error ? {error} : null} +
+ {error ? {error} : null}
@@ -302,15 +341,11 @@ const PopupNew = ({ ) } -const PopupDelete = ({open, handleClose, handleOkDel}: PopupType) => { - if(!handleOkDel) return null +const PopupDelete = ({ open, handleClose, handleOkDel }: PopupType) => { + if (!handleOkDel) return null return ( - + Do you want delete? @@ -321,35 +356,16 @@ const PopupDelete = ({open, handleClose, handleOkDel}: PopupType) => { ) } -const PopupSave = ({open, handleClose, handleOkSave}: PopupType) => { - return ( - - - Do you want save? - - - - - - - ) -} - const Workspaces = () => { const dispatch = useDispatch() const loading = useSelector(selectIsLoadingWorkspaceList) const data = useSelector(selectWorkspaceData) const user = useSelector(selectCurrentUser) - const [open, setOpen] = useState({share: false, del: false, new: false, save: false}) + const [open, setOpen] = useState({ share: false, del: false, new: false }) const [idDel, setIdDel] = useState() const [newWorkspace, setNewWorkSpace] = useState() - const [error, setError] = useState("") - const [initName, setInitName] = useState("") - const [rowModesModel, setRowModesModel] = useState({}); + const [error, setError] = useState('') + const [rowModesModel, setRowModesModel] = useState({}) const [searchParams, setParams] = useSearchParams() const offset = searchParams.get('offset') @@ -369,78 +385,66 @@ const Workspaces = () => { }, [dataParams]) const handleOpenPopupShare = () => { - setOpen({...open, share: true}) + setOpen({ ...open, share: true }) } const handleClosePopupShare = () => { - setOpen({...open, share: false}) + setOpen({ ...open, share: false }) } const handleOpenPopupDel = (id: number) => { setIdDel(id) - setOpen({...open, del: true}) + setOpen({ ...open, del: true }) } const handleOkDel = async () => { - if(!idDel) return - await dispatch(delWorkspace({id: idDel, params: dataParams})) - setOpen({...open, del: false}) + if (!idDel) return + await dispatch(delWorkspace({ id: idDel, params: dataParams })) + setOpen({ ...open, del: false }) } const handleClosePopupDel = () => { - setOpen({...open, del: false}) + setOpen({ ...open, del: false }) } const handleOpenPopupNew = () => { - setOpen({...open, new: true}) + setOpen({ ...open, new: true }) } const handleClosePopupNew = () => { - setOpen({...open, new: false}) - setError("") + setOpen({ ...open, new: false }) + setError('') } - const handleClosePopupSave = () => { - setRowModesModel({ - ...rowModesModel, - [Object.keys(rowModesModel)[0]]: { mode: 'view', ignoreModifications: true } as any, - }); - setOpen({...open, save: false}) - } - - const handleOkSave = async () => { - setRowModesModel({ - ...rowModesModel, - [Object.keys(rowModesModel)[0]]: { mode: 'view' } as any, - }); + const onEditName = (id: number) => { + setRowModesModel((pre) => ({ ...pre, [id]: { mode: GridRowModes.Edit } })) } const handleOkNew = async () => { - if(!newWorkspace) { - setError("is not empty") + if (!newWorkspace) { + setError('is not empty') return } - await dispatch(postWorkspace({name: newWorkspace, params: dataParams})) - setOpen({...open, new: false}) - setError("") - setNewWorkSpace("") + await dispatch(postWorkspace({ name: newWorkspace })) + await dispatch(getWorkspaceList(dataParams)) + setOpen({ ...open, new: false }) + setError('') + setNewWorkSpace('') } const onProcessRowUpdateError = (newRow: any) => { return newRow - }; + } const handleFileUpload = async (event: ChangeEvent) => { dispatch(importWorkspace({})) } const pagi = useCallback( - (page?: number) => { - return `limit=${data.limit}&offset=${ - page ? page - 1 : data.offset - }` - }, - [data.limit, data.offset], + (page?: number) => { + return `limit=${data.limit}&offset=${page ? page - 1 : data.offset}` + }, + [data.limit, data.offset], ) const handlePage = (e: ChangeEvent, page: number) => { @@ -452,26 +456,18 @@ const Workspaces = () => { } const handleRowModesModelChange = (newRowModesModel: GridRowModesModel) => { - setRowModesModel(newRowModesModel); - }; + setRowModesModel(newRowModesModel) + } - const onRowEditStop: GridEventListener<"rowEditStop"> = (params, event) => { - setInitName(params.row.name) + const onRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { if (params.reason === GridRowEditStopReasons.rowFocusOut) { - event.defaultMuiPrevented = true; + event.defaultMuiPrevented = true } - //todo pending save state row edit - setOpen({...open, save: true}) - }; + } const processRowUpdate = async (newRow: GridRowModel) => { - if(!newRow.name) { - alert("Workspace Name cann't empty") - setOpen({...open, save: false}) - return { ...newRow, name: initName} - } - await dispatch(putWorkspace({name: newRow.name, id: Number(Object.keys(rowModesModel)[0]), params: dataParams})) - setOpen({...open, save: false}) + await dispatch(putWorkspace({ name: newRow.name, id: newRow.id })) + await dispatch(getWorkspaceList(dataParams)) return newRow } @@ -480,22 +476,23 @@ const Workspaces = () => { Workspaces
@@ -507,7 +507,7 @@ const Workspaces = () => { }} > Date: Fri, 28 Jul 2023 12:30:30 +0700 Subject: [PATCH 18/29] add isAnyOfin database --- .../src/components/Database/DatabaseCells.tsx | 1 - .../Database/DatabaseExperiments.tsx | 1 - .../src/store/slice/Database/DatabaseSlice.ts | 67 ++++++++++--------- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/Database/DatabaseCells.tsx b/frontend/src/components/Database/DatabaseCells.tsx index 09bf08789..bc62aa494 100644 --- a/frontend/src/components/Database/DatabaseCells.tsx +++ b/frontend/src/components/Database/DatabaseCells.tsx @@ -3,7 +3,6 @@ import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react' import { useSearchParams } from 'react-router-dom' import DialogImage from '../common/DialogImage' import { - GridCallbackDetails, GridEnrichedColDef, GridFilterModel, GridSortDirection, diff --git a/frontend/src/components/Database/DatabaseExperiments.tsx b/frontend/src/components/Database/DatabaseExperiments.tsx index 527ca4696..88dded77d 100644 --- a/frontend/src/components/Database/DatabaseExperiments.tsx +++ b/frontend/src/components/Database/DatabaseExperiments.tsx @@ -10,7 +10,6 @@ import DialogContent from '@mui/material/DialogContent' import DialogContentText from '@mui/material/DialogContentText' import SwitchCustom from '../common/SwitchCustom' import { - GridCallbackDetails, GridEnrichedColDef, GridFilterModel, GridSortDirection, diff --git a/frontend/src/store/slice/Database/DatabaseSlice.ts b/frontend/src/store/slice/Database/DatabaseSlice.ts index 6f2f28f8d..54b51ed77 100644 --- a/frontend/src/store/slice/Database/DatabaseSlice.ts +++ b/frontend/src/store/slice/Database/DatabaseSlice.ts @@ -1,4 +1,4 @@ -import { createSlice } from '@reduxjs/toolkit' +import { createSlice, isAnyOf } from '@reduxjs/toolkit' import { getExperimentsDatabase, getCellsDatabase, @@ -18,18 +18,18 @@ const initData = { } export type TypeData = { - public: DatabaseDTO, + public: DatabaseDTO private: DatabaseDTO } export const initialState: { - data: TypeData, + data: TypeData loading: boolean type: 'experiment' | 'cell' } = { data: { public: initData, - private: initData + private: initData, }, loading: false, type: 'experiment', @@ -69,34 +69,37 @@ export const databaseSlice = createSlice({ } state.loading = true }) - .addCase(getExperimentsDatabase.fulfilled, (state, action) => { - state.data.private = action.payload - state.loading = false - }) - .addCase(getCellsDatabase.fulfilled, (state, action) => { - state.data.private = action.payload - state.loading = false - }) - .addCase(getExperimentsPublicDatabase.fulfilled, (state, action) => { - state.data.public = action.payload - state.loading = false - }) - .addCase(getCellsPublicDatabase.fulfilled, (state, action) => { - state.data.public = action.payload - state.loading = false - }) - .addCase(getExperimentsDatabase.rejected, (state, action) => { - state.loading = false - }) - .addCase(getCellsDatabase.rejected, (state, action) => { - state.loading = false - }) - .addCase(getExperimentsPublicDatabase.rejected, (state, action) => { - state.loading = false - }) - .addCase(getCellsPublicDatabase.rejected, (state, action) => { - state.loading = false - }) + .addMatcher( + isAnyOf( + getCellsDatabase.fulfilled, + getExperimentsDatabase.fulfilled, + ), + (state, action) => { + state.data.private = action.payload + state.loading = false + }, + ) + .addMatcher( + isAnyOf( + getCellsPublicDatabase.fulfilled, + getExperimentsPublicDatabase.fulfilled, + ), + (state, action) => { + state.data.public = action.payload + state.loading = false + }, + ) + .addMatcher( + isAnyOf( + getExperimentsDatabase.rejected, + getCellsDatabase.rejected, + getExperimentsPublicDatabase.rejected, + getCellsPublicDatabase.rejected, + ), + (state) => { + state.loading = false + }, + ) }, }) From 07c01e13bd48385afecbaed366b64b1ea40d8438 Mon Sep 17 00:00:00 2001 From: SangLv Date: Fri, 28 Jul 2023 13:31:01 +0700 Subject: [PATCH 19/29] rename DataRequestEdit to WorkspacePostDataDTO, remove delWorkspace.fulfilled to slice workspace --- frontend/src/api/Workspace/index.ts | 19 +++++-- .../slice/Workspace/WorkspaceSelector.ts | 3 - .../store/slice/Workspace/WorkspaceSlice.ts | 1 - .../slice/Workspace/WorkspacesActions.ts | 56 +++++++++---------- 4 files changed, 40 insertions(+), 39 deletions(-) diff --git a/frontend/src/api/Workspace/index.ts b/frontend/src/api/Workspace/index.ts index 2e494f5c8..4ba16b9ad 100644 --- a/frontend/src/api/Workspace/index.ts +++ b/frontend/src/api/Workspace/index.ts @@ -1,7 +1,8 @@ import axios from 'utils/axios' import qs from 'qs' +import { ItemsWorkspace } from 'store/slice/Workspace/WorkspaceType' -export type DataRequestEdit = { name: string; id?: number } +export type WorkspacePostDataDTO = { name: string; id?: number } export const getWorkspacesApi = async (params: { [key: string]: number }) => { const paramsNew = qs.stringify(params, { indices: false }) @@ -9,27 +10,33 @@ export const getWorkspacesApi = async (params: { [key: string]: number }) => { return response.data } -export const delWorkspaceApi = async (id: number) => { +export const delWorkspaceApi = async (id: number): Promise => { const response = await axios.delete(`/workspace/${id}`) return response.data } -export const postWorkspaceApi = async (data: DataRequestEdit) => { +export const postWorkspaceApi = async ( + data: WorkspacePostDataDTO, +): Promise => { const response = await axios.post(`/workspace`, data) return response.data } -export const putWorkspaceApi = async (data: DataRequestEdit) => { +export const putWorkspaceApi = async ( + data: WorkspacePostDataDTO, +): Promise => { const response = await axios.put(`/workspace/${data.id}`, { name: data.name }) return response.data } -export const importWorkspaceApi = async (data: Object) => { +export const importWorkspaceApi = async ( + data: Object, +): Promise => { const response = await axios.post(`/workspace/import`, { todo_dummy: data }) return response.data } -export const exportWorkspaceApi = async (id: number) => { +export const exportWorkspaceApi = async (id: number): Promise => { const response = await axios.get(`/workspace/export/${id}`) return response.data } diff --git a/frontend/src/store/slice/Workspace/WorkspaceSelector.ts b/frontend/src/store/slice/Workspace/WorkspaceSelector.ts index e47808fcf..6f011300c 100644 --- a/frontend/src/store/slice/Workspace/WorkspaceSelector.ts +++ b/frontend/src/store/slice/Workspace/WorkspaceSelector.ts @@ -9,8 +9,5 @@ export const selectActiveTab = (state: RootState) => export const selectCurrentWorkspaceId = (state: RootState) => state.workspace.currentWorkspace.workspaceId -export const selectWorkspaceList = (state: RootState) => - state.workspace.workspace - export const selectIsLoadingWorkspaceList = (state: RootState) => state.workspace.loading diff --git a/frontend/src/store/slice/Workspace/WorkspaceSlice.ts b/frontend/src/store/slice/Workspace/WorkspaceSlice.ts index a7d346bfb..59c7c254d 100644 --- a/frontend/src/store/slice/Workspace/WorkspaceSlice.ts +++ b/frontend/src/store/slice/Workspace/WorkspaceSlice.ts @@ -66,7 +66,6 @@ export const workspaceSlice = createSlice({ postWorkspace.pending, putWorkspace.pending, delWorkspace.pending, - delWorkspace.fulfilled, ), (state) => { state.loading = true diff --git a/frontend/src/store/slice/Workspace/WorkspacesActions.ts b/frontend/src/store/slice/Workspace/WorkspacesActions.ts index d0014b32d..440cfadc5 100644 --- a/frontend/src/store/slice/Workspace/WorkspacesActions.ts +++ b/frontend/src/store/slice/Workspace/WorkspacesActions.ts @@ -1,6 +1,6 @@ import { createAsyncThunk } from '@reduxjs/toolkit' import { - DataRequestEdit, + WorkspacePostDataDTO, delWorkspaceApi, exportWorkspaceApi, getWorkspacesApi, @@ -15,12 +15,10 @@ import { WORKSPACE_SLICE_NAME, } from './WorkspaceType' -// @ts-ignore export const getWorkspaceList = createAsyncThunk< WorkspaceDataDTO, { [key: string]: number } ->(`${WORKSPACE_SLICE_NAME}/getWorkspaceList`, async (params, thunkAPI) => { - // const { rejectWithValue } = thunkAPI +>(`${WORKSPACE_SLICE_NAME}/getWorkspaceList`, async (params) => { try { const response = await getWorkspacesApi(params as { [key: string]: number }) return response @@ -43,31 +41,31 @@ export const delWorkspace = createAsyncThunk( }, ) -export const postWorkspace = createAsyncThunk( - `${WORKSPACE_SLICE_NAME}/postWorkspaceList`, - async (data, thunkAPI) => { - const { rejectWithValue } = thunkAPI - try { - const response = await postWorkspaceApi(data) - return response - } catch (e) { - return rejectWithValue(e) - } - }, -) +export const postWorkspace = createAsyncThunk< + ItemsWorkspace, + WorkspacePostDataDTO +>(`${WORKSPACE_SLICE_NAME}/postWorkspaceList`, async (data, thunkAPI) => { + const { rejectWithValue } = thunkAPI + try { + const response = await postWorkspaceApi(data) + return response + } catch (e) { + return rejectWithValue(e) + } +}) -export const putWorkspace = createAsyncThunk( - `${WORKSPACE_SLICE_NAME}/putWorkspaceList`, - async (data, thunkAPI) => { - const { rejectWithValue } = thunkAPI - try { - const response = await putWorkspaceApi(data) - return response - } catch (e) { - return rejectWithValue(e) - } - }, -) +export const putWorkspace = createAsyncThunk< + ItemsWorkspace, + WorkspacePostDataDTO +>(`${WORKSPACE_SLICE_NAME}/putWorkspaceList`, async (data, thunkAPI) => { + const { rejectWithValue } = thunkAPI + try { + const response = await putWorkspaceApi(data) + return response + } catch (e) { + return rejectWithValue(e) + } +}) export const importWorkspace = createAsyncThunk< ItemsWorkspace, @@ -83,7 +81,7 @@ export const importWorkspace = createAsyncThunk< } }) -export const exportWorkspace = createAsyncThunk( +export const exportWorkspace = createAsyncThunk( `${WORKSPACE_SLICE_NAME}/exportWorkspaceList`, async (id, thunkAPI) => { const { rejectWithValue } = thunkAPI From 165461bef32ce96f8f288927b8792e04429f1b41 Mon Sep 17 00:00:00 2001 From: SangLv Date: Fri, 28 Jul 2023 13:34:54 +0700 Subject: [PATCH 20/29] add type return getWorkspacesApi --- frontend/src/api/Workspace/index.ts | 4 ++-- frontend/src/store/slice/Workspace/WorkspacesActions.ts | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/api/Workspace/index.ts b/frontend/src/api/Workspace/index.ts index 4ba16b9ad..f0492e756 100644 --- a/frontend/src/api/Workspace/index.ts +++ b/frontend/src/api/Workspace/index.ts @@ -1,10 +1,10 @@ import axios from 'utils/axios' import qs from 'qs' -import { ItemsWorkspace } from 'store/slice/Workspace/WorkspaceType' +import { ItemsWorkspace, WorkspaceDataDTO } from 'store/slice/Workspace/WorkspaceType' export type WorkspacePostDataDTO = { name: string; id?: number } -export const getWorkspacesApi = async (params: { [key: string]: number }) => { +export const getWorkspacesApi = async (params: { [key: string]: number }): Promise => { const paramsNew = qs.stringify(params, { indices: false }) const response = await axios.get(`/workspaces?${paramsNew}`) return response.data diff --git a/frontend/src/store/slice/Workspace/WorkspacesActions.ts b/frontend/src/store/slice/Workspace/WorkspacesActions.ts index 440cfadc5..9e5632c7a 100644 --- a/frontend/src/store/slice/Workspace/WorkspacesActions.ts +++ b/frontend/src/store/slice/Workspace/WorkspacesActions.ts @@ -18,12 +18,13 @@ import { export const getWorkspaceList = createAsyncThunk< WorkspaceDataDTO, { [key: string]: number } ->(`${WORKSPACE_SLICE_NAME}/getWorkspaceList`, async (params) => { +>(`${WORKSPACE_SLICE_NAME}/getWorkspaceList`, async (params, thunkAPI) => { + const { rejectWithValue } = thunkAPI try { - const response = await getWorkspacesApi(params as { [key: string]: number }) + const response = await getWorkspacesApi(params) return response } catch (e) { - return null + return rejectWithValue(e) } }) From 799c6938819197ef37049206d91549f2b09d9360 Mon Sep 17 00:00:00 2001 From: SangLv Date: Fri, 28 Jul 2023 19:12:27 +0700 Subject: [PATCH 21/29] rollback user_id to Workspace(BaseModel) --- studio/app/common/schemas/workspace.py | 1 + 1 file changed, 1 insertion(+) diff --git a/studio/app/common/schemas/workspace.py b/studio/app/common/schemas/workspace.py index a3df2daba..574cb5ba7 100644 --- a/studio/app/common/schemas/workspace.py +++ b/studio/app/common/schemas/workspace.py @@ -9,6 +9,7 @@ class Workspace(BaseModel): id: Optional[int] name: str + user_id: Optional[int] user: Optional[UserInfo] created_at: Optional[datetime] updated_at: Optional[datetime] From 7209c27159137e20d5a5c91c34f10b7dd99aeb24 Mon Sep 17 00:00:00 2001 From: SangLv Date: Mon, 31 Jul 2023 11:37:56 +0700 Subject: [PATCH 22/29] validate Workspace Name --- frontend/src/pages/Workspace/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 4973b572a..dae0d9042 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -461,6 +461,10 @@ const Workspaces = () => { } const processRowUpdate = async (newRow: GridRowModel) => { + if(!newRow.name) { + alert("Workspace Name cann't empty") + return + } await dispatch(putWorkspace({ name: newRow.name, id: newRow.id })) await dispatch(getWorkspaceList(dataParams)) return newRow From 77371adc6f7ef0096f664083c7942de9be683ec9 Mon Sep 17 00:00:00 2001 From: SangLv Date: Mon, 31 Jul 2023 15:16:05 +0700 Subject: [PATCH 23/29] fix edit name --- frontend/src/pages/Workspace/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index dae0d9042..3f8118f1e 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -360,6 +360,7 @@ const Workspaces = () => { const [idDel, setIdDel] = useState() const [newWorkspace, setNewWorkSpace] = useState() const [error, setError] = useState('') + const [initName, setInitName] = useState("") const [rowModesModel, setRowModesModel] = useState({}) const [searchParams, setParams] = useSearchParams() @@ -455,6 +456,7 @@ const Workspaces = () => { } const onRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { + setInitName(params.row.name) if (params.reason === GridRowEditStopReasons.rowFocusOut) { event.defaultMuiPrevented = true } @@ -463,7 +465,7 @@ const Workspaces = () => { const processRowUpdate = async (newRow: GridRowModel) => { if(!newRow.name) { alert("Workspace Name cann't empty") - return + return {...newRow, name: initName} } await dispatch(putWorkspace({ name: newRow.name, id: newRow.id })) await dispatch(getWorkspaceList(dataParams)) From 9717e920fb7b523623f1a9f8526ac7eead379a70 Mon Sep 17 00:00:00 2001 From: SangLv Date: Mon, 31 Jul 2023 15:48:52 +0700 Subject: [PATCH 24/29] fix button icon --- frontend/src/pages/Workspace/index.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 3f8118f1e..3dcc89279 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -9,7 +9,6 @@ import { DialogActions, Input, Pagination, - IconButton, } from '@mui/material' import { GridRenderCellParams, GridRowParams, DataGrid } from '@mui/x-data-grid' import { @@ -99,9 +98,9 @@ const columns = ( {value} {row.user?.id === user?.id && ( - onEdit?.(row.id)}> - - + onEdit?.(row.id)}> + + )}
) @@ -605,4 +604,15 @@ const DialogCustom = styled(Dialog)(({ theme }) => ({ }, })) +const ButtonIcon = styled('button')(({theme}) => ({ + width: '32px', + height: '32px', + border: 'none', + borderRadius: '50%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer' +})) + export default Workspaces From 2907dd8367db345450726d6e1ae20a95fe650846 Mon Sep 17 00:00:00 2001 From: SangLv Date: Mon, 31 Jul 2023 15:57:42 +0700 Subject: [PATCH 25/29] fix background color button edit --- frontend/src/pages/Workspace/index.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 3dcc89279..75474c818 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -605,6 +605,8 @@ const DialogCustom = styled(Dialog)(({ theme }) => ({ })) const ButtonIcon = styled('button')(({theme}) => ({ + minWidth: '32px', + minHeight: '32px', width: '32px', height: '32px', border: 'none', @@ -612,7 +614,12 @@ const ButtonIcon = styled('button')(({theme}) => ({ display: 'flex', alignItems: 'center', justifyContent: 'center', - cursor: 'pointer' + cursor: 'pointer', + background: 'transparent', + '&:hover': { + background: 'rgb(239 239 239)' + } })) + export default Workspaces From b33a244c144dc2664b3ed18cbb06e47440088e12 Mon Sep 17 00:00:00 2001 From: SangLv Date: Mon, 31 Jul 2023 16:42:51 +0700 Subject: [PATCH 26/29] fix onblur row --- frontend/src/pages/Workspace/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 75474c818..27a731617 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -456,9 +456,6 @@ const Workspaces = () => { const onRowEditStop: GridEventListener<'rowEditStop'> = (params, event) => { setInitName(params.row.name) - if (params.reason === GridRowEditStopReasons.rowFocusOut) { - event.defaultMuiPrevented = true - } } const processRowUpdate = async (newRow: GridRowModel) => { @@ -466,6 +463,7 @@ const Workspaces = () => { alert("Workspace Name cann't empty") return {...newRow, name: initName} } + if(newRow.name === initName) return newRow await dispatch(putWorkspace({ name: newRow.name, id: newRow.id })) await dispatch(getWorkspaceList(dataParams)) return newRow From 7b837de0dd257294529d3ac4fdc0f5e1b34534f6 Mon Sep 17 00:00:00 2001 From: SangLv Date: Mon, 31 Jul 2023 18:17:42 +0700 Subject: [PATCH 27/29] add onCellClick. cancel edit name --- frontend/src/pages/Workspace/index.tsx | 30 ++++++++++++++++++-------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/frontend/src/pages/Workspace/index.tsx b/frontend/src/pages/Workspace/index.tsx index 27a731617..68f9b891e 100644 --- a/frontend/src/pages/Workspace/index.tsx +++ b/frontend/src/pages/Workspace/index.tsx @@ -13,7 +13,6 @@ import { import { GridRenderCellParams, GridRowParams, DataGrid } from '@mui/x-data-grid' import { DataGridPro, - GridRowEditStopReasons, GridEventListener, GridRowModesModel, GridRowModel, @@ -359,7 +358,7 @@ const Workspaces = () => { const [idDel, setIdDel] = useState() const [newWorkspace, setNewWorkSpace] = useState() const [error, setError] = useState('') - const [initName, setInitName] = useState("") + const [initName, setInitName] = useState('') const [rowModesModel, setRowModesModel] = useState({}) const [searchParams, setParams] = useSearchParams() @@ -458,12 +457,25 @@ const Workspaces = () => { setInitName(params.row.name) } + const onCellClick: GridEventListener<'cellClick'> | undefined = (event) => { + if (event.field === 'name') return + setRowModesModel((pre) => { + const object: GridRowModesModel = {} + Object.keys(pre).forEach(key => { + object[key] = { + mode: GridRowModes.View, ignoreModifications: true + } + }) + return object + }) + } + const processRowUpdate = async (newRow: GridRowModel) => { - if(!newRow.name) { + if (!newRow.name) { alert("Workspace Name cann't empty") - return {...newRow, name: initName} + return { ...newRow, name: initName } } - if(newRow.name === initName) return newRow + if (newRow.name === initName) return newRow await dispatch(putWorkspace({ name: newRow.name, id: newRow.id })) await dispatch(getWorkspaceList(dataParams)) return newRow @@ -515,6 +527,7 @@ const Workspaces = () => { // sortingMode={'server'} // onSortModelChange={handleSort} // onFilterModelChange={handleFilter as any} + onCellClick={onCellClick} rows={data?.items} editMode="row" rowModesModel={rowModesModel} @@ -602,7 +615,7 @@ const DialogCustom = styled(Dialog)(({ theme }) => ({ }, })) -const ButtonIcon = styled('button')(({theme}) => ({ +const ButtonIcon = styled('button')(({ theme }) => ({ minWidth: '32px', minHeight: '32px', width: '32px', @@ -615,9 +628,8 @@ const ButtonIcon = styled('button')(({theme}) => ({ cursor: 'pointer', background: 'transparent', '&:hover': { - background: 'rgb(239 239 239)' - } + background: 'rgb(239 239 239)', + }, })) - export default Workspaces From 9e526a4cd666f1e60e481d3ccf365f9c82a709d1 Mon Sep 17 00:00:00 2001 From: quanpython Date: Mon, 31 Jul 2023 21:57:37 +0700 Subject: [PATCH 28/29] add user search API --- studio/__main_unit__.py | 2 ++ studio/app/common/routers/users_search.py | 40 +++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 studio/app/common/routers/users_search.py diff --git a/studio/__main_unit__.py b/studio/__main_unit__.py index e6671fdbc..04485b5a8 100644 --- a/studio/__main_unit__.py +++ b/studio/__main_unit__.py @@ -17,6 +17,7 @@ run, users_admin, users_me, + users_search, workspace, ) from studio.app.dir_path import DIRPATH @@ -36,6 +37,7 @@ app.include_router(run.router) app.include_router(users_admin.router) app.include_router(users_me.router) +app.include_router(users_search.router) app.include_router(workspace.router) # optinist routers diff --git a/studio/app/common/routers/users_search.py b/studio/app/common/routers/users_search.py new file mode 100644 index 000000000..8e5c8079e --- /dev/null +++ b/studio/app/common/routers/users_search.py @@ -0,0 +1,40 @@ +from typing import List + +from fastapi import APIRouter, Depends, Query +from sqlmodel import Session, or_ + +from studio.app.common.core.auth.auth_dependencies import get_current_user +from studio.app.common.db.database import get_db +from studio.app.common.models import User as UserModel +from studio.app.common.schemas.users import UserInfo + +router = APIRouter(prefix="/users/search", tags=["users/search"]) + + +@router.get( + "/share_users", + response_model=List[UserInfo], + dependencies=[Depends(get_current_user)], + description=""" +- Get a list of users with whom content is shared. +- Note: Maximum of 10 responses. (security considerations) +""", +) +def search_share_users( + keyword: str = Query(description="partial match (user.name or user.email)"), + db: Session = Depends(get_db), +): + MAX_RESPONSE_COUNT = 10 + users = ( + db.query(UserModel) + .filter( + or_( + UserModel.name.like("%{0}%".format(keyword)), + UserModel.email.like("%{0}%".format(keyword)), + ) + ) + .order_by(UserModel.id) + .limit(MAX_RESPONSE_COUNT) + .all() + ) + return users From 8d527d4c7ecbdddc3e1ab3474ea2810d2b81741a Mon Sep 17 00:00:00 2001 From: quanpython Date: Tue, 1 Aug 2023 11:04:06 +0700 Subject: [PATCH 29/29] check active status and organization id --- studio/app/common/routers/users_search.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/studio/app/common/routers/users_search.py b/studio/app/common/routers/users_search.py index 8e5c8079e..f98231ee0 100644 --- a/studio/app/common/routers/users_search.py +++ b/studio/app/common/routers/users_search.py @@ -6,7 +6,7 @@ from studio.app.common.core.auth.auth_dependencies import get_current_user from studio.app.common.db.database import get_db from studio.app.common.models import User as UserModel -from studio.app.common.schemas.users import UserInfo +from studio.app.common.schemas.users import User, UserInfo router = APIRouter(prefix="/users/search", tags=["users/search"]) @@ -14,7 +14,6 @@ @router.get( "/share_users", response_model=List[UserInfo], - dependencies=[Depends(get_current_user)], description=""" - Get a list of users with whom content is shared. - Note: Maximum of 10 responses. (security considerations) @@ -23,10 +22,15 @@ def search_share_users( keyword: str = Query(description="partial match (user.name or user.email)"), db: Session = Depends(get_db), + current_user: User = Depends(get_current_user), ): MAX_RESPONSE_COUNT = 10 users = ( db.query(UserModel) + .filter( + UserModel.active.is_(True), + UserModel.organization_id == current_user.organization_id, + ) .filter( or_( UserModel.name.like("%{0}%".format(keyword)),