diff --git a/src/components/projects/projectId/knowledge-graphs/DeleteKnowledgeGraphModal.tsx b/src/components/projects/projectId/knowledge-graphs/DeleteKnowledgeGraphModal.tsx new file mode 100644 index 00000000..c5ae6e12 --- /dev/null +++ b/src/components/projects/projectId/knowledge-graphs/DeleteKnowledgeGraphModal.tsx @@ -0,0 +1,42 @@ +import Modal from "@/src/components/shared/modal/Modal"; +import { selectModal } from "@/src/reduxStore/states/modal"; +import { selectKnowledgeGraphsAll } from "@/src/reduxStore/states/pages/knowledge-graphs"; +import { selectProjectId } from "@/src/reduxStore/states/project"; +import { deleteKnowledgeGraphById } from "@/src/services/base/knowledge-graphs"; +import { DeleteKnowledgeGraphModalProps } from "@/src/types/components/projects/projectId/knowledge-graphs/knowledge-graphs"; +import { ModalButton, ModalEnum } from "@/src/types/shared/modal"; +import { useCallback, useEffect, useState } from "react"; +import { useSelector } from "react-redux"; + +const ABORT_BUTTON = { buttonCaption: "Delete", useButton: true, disabled: false }; + +export default function DeleteKnowledgeGraphModal(props: DeleteKnowledgeGraphModalProps) { + const projectId = useSelector(selectProjectId); + const modalDelete = useSelector(selectModal(ModalEnum.DELETE_KNOWLEDGE_GRAPH)); + const knowledgeGraphs = useSelector(selectKnowledgeGraphsAll); + + const [abortButton, setAbortButton] = useState(ABORT_BUTTON); + + const deleteKnowledgeGraphs = useCallback(() => { + knowledgeGraphs.forEach((knowledgeGraph) => { + if (knowledgeGraph.selected) { + deleteKnowledgeGraphById(projectId, knowledgeGraph.id, (res) => { + props.refetch(); + }) + } + }); + }, [modalDelete]); + + useEffect(() => { + setAbortButton({ ...ABORT_BUTTON, emitFunction: deleteKnowledgeGraphs }); + }, [modalDelete]); + + return ( +

Warning

+
+ Are you sure you want to delete selected {props.countSelected <= 1 ? 'knowledge graph' : 'knowledge graphs'}? + Currently selected {props.countSelected <= 1 ? 'is' : 'are'}: + {props.selectionList} +
+
) +} \ No newline at end of file diff --git a/src/components/projects/projectId/knowledge-graphs/KnowledgeGraphsGridCards.tsx b/src/components/projects/projectId/knowledge-graphs/KnowledgeGraphsGridCards.tsx new file mode 100644 index 00000000..2a825996 --- /dev/null +++ b/src/components/projects/projectId/knowledge-graphs/KnowledgeGraphsGridCards.tsx @@ -0,0 +1,28 @@ +import { useSelector } from "react-redux"; +import { KnowledgeGraph } from "@/src/types/components/projects/projectId/knowledge-graphs/knowledge-graphs"; +import { selectKnowledgeGraphsAll } from "@/src/reduxStore/states/pages/knowledge-graphs"; + +export default function KnowledgeGraphsGridCards() { + const knowledgeGraphs = useSelector(selectKnowledgeGraphsAll); + return (<> + {knowledgeGraphs.map((knowledgeGraph: KnowledgeGraph, index: number) => (
+
+
+ {/* TODO: add data */} +
+ +
+
+
{knowledgeGraph.name}
+
+
+
+ {knowledgeGraph.description} +
+
+
+
+
)) + } + ); +} \ No newline at end of file diff --git a/src/components/projects/projectId/knowledge-graphs/KnowledgeGraphsHeader.tsx b/src/components/projects/projectId/knowledge-graphs/KnowledgeGraphsHeader.tsx new file mode 100644 index 00000000..ac095159 --- /dev/null +++ b/src/components/projects/projectId/knowledge-graphs/KnowledgeGraphsHeader.tsx @@ -0,0 +1,99 @@ +import { openModal, selectModal } from '@/src/reduxStore/states/modal'; +import style from '@/src/styles/components/projects/projectId/heuristics/heuristics.module.css'; +import { ModalEnum } from '@/src/types/shared/modal'; +import { TOOLTIPS_DICT } from '@/src/util/tooltip-constants'; +import { Tooltip } from '@nextui-org/react'; +import { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import KernDropdown from '@/submodules/react-components/components/KernDropdown'; +import KernButton from "@/submodules/react-components/components/kern-button/KernButton"; +import { ChevronDownIcon } from "@heroicons/react/20/solid"; +import DeleteKnowledgeGraphModal from './DeleteKnowledgeGraphModal'; +import { KnowledgeGraphsHeaderProps, KnowledgeGraphType } from '@/src/types/components/projects/projectId/knowledge-graphs/knowledge-graphs'; +import { selectKnowledgeGraphsAll, setKnowledgeGraphType } from '@/src/reduxStore/states/pages/knowledge-graphs'; + +const KNOWLEDGE_GRAPHS_TYPES = Object.values(KnowledgeGraphType); +const ACTIONS_DROPDOWN_OPTIONS = ['Select all', 'Deselect all', 'Delete selected']; + +export default function KnowledgeGraphsHeader(props: KnowledgeGraphsHeaderProps) { + const dispatch = useDispatch(); + + const knowledgeGraphs = useSelector(selectKnowledgeGraphsAll); + const modalDelete = useSelector(selectModal(ModalEnum.DELETE_KNOWLEDGE_GRAPH)); + + const [selectionList, setSelectionList] = useState(''); + const [countSelected, setCountSelected] = useState(0); + + useEffect(() => { + if (!modalDelete) return; + prepareSelectionList(); + }, [modalDelete]); + + const executeOption = useCallback((option: string) => { + switch (option) { + case KnowledgeGraphType.LIVE: + dispatch(setKnowledgeGraphType(KnowledgeGraphType.LIVE)); + break; + case KnowledgeGraphType.STABLE: + dispatch(setKnowledgeGraphType(KnowledgeGraphType.STABLE)); + break; + case 'Select all': + selectKnowledgeGraphs(true); + break; + case 'Deselect all': + selectKnowledgeGraphs(false); + break; + case 'Delete selected': + prepareSelectionList(); + dispatch(openModal(ModalEnum.DELETE_KNOWLEDGE_GRAPH)); + break; + } + }, []); + + const selectKnowledgeGraphs = useCallback((checked: boolean) => { + knowledgeGraphs.forEach((knowledgeGraph, index) => { + knowledgeGraphs[index].selected = checked; + }); + prepareSelectionList(); + }, [knowledgeGraphs]); + + const prepareSelectionList = useCallback(() => { + let selectionListFinal = ''; + let countSelected = 0; + knowledgeGraphs.forEach((knowledgeGraph, index) => { + if (knowledgeGraph.selected) { + selectionListFinal += knowledgeGraphs[index].name; + selectionListFinal += '\n'; + countSelected++; + } + }); + setCountSelected(countSelected) + setSelectionList(selectionListFinal); + }, [knowledgeGraphs]); + + return ( +
+
+ executeOption(option)} buttonClasses={`${style.actionsHeight} text-xs whitespace-nowrap`} dropdownClasses="mr-3" dropdownItemsWidth='w-48' dropdownWidth='w-48' + iconsArray={['IconCode', 'IconBolt']} useFillForIcons={[false, true]} /> + + {knowledgeGraphs && knowledgeGraphs.length > 0 ? ( + !checked.selected)]} + selectedOption={(option: string) => executeOption(option)} dropdownClasses="mr-3" buttonClasses={`${style.actionsHeight} text-xs`} dropdownItemsWidth='w-40' dropdownWidth='w-32' + iconsArray={['IconSquareCheck', 'IconSquare', 'IconTrash']} /> + ) : ( + + )} +
+ + +
+ ) +} \ No newline at end of file diff --git a/src/components/projects/projectId/knowledge-graphs/KnowledgeGraphsOverview.tsx b/src/components/projects/projectId/knowledge-graphs/KnowledgeGraphsOverview.tsx new file mode 100644 index 00000000..5e6ae993 --- /dev/null +++ b/src/components/projects/projectId/knowledge-graphs/KnowledgeGraphsOverview.tsx @@ -0,0 +1,51 @@ +import { selectProjectId } from "@/src/reduxStore/states/project"; +import { useDispatch, useSelector } from "react-redux"; +import { useCallback } from "react"; +import { selectOrganizationId } from "@/src/reduxStore/states/general"; +import { useWebsocket } from "@/submodules/react-components/hooks/web-socket/useWebsocket"; +import { Application, CurrentPage } from "@/submodules/react-components/hooks/web-socket/constants"; +import { selectKnowledgeGraphsAll, setAllKnowledgeGraphs } from "@/src/reduxStore/states/pages/knowledge-graphs"; +import KnowledgeGraphsHeader from "./KnowledgeGraphsHeader"; +import { getKnowledgeGraphs } from "@/src/services/base/knowledge-graphs"; +import KnowledgeGraphsGridCards from "./KnowledgeGraphsGridCards"; + +export function KnowledgeGraphsOverview() { + const dispatch = useDispatch(); + + const projectId = useSelector(selectProjectId); + const knowledgeGraphs = useSelector(selectKnowledgeGraphsAll); + + const refetchKnowledgeGraphs = useCallback(() => { + getKnowledgeGraphs(projectId, (res) => { + dispatch(setAllKnowledgeGraphs(res)); + }); + }, [projectId]); + const handleWebsocketNotification = useCallback((msgParts: string[]) => { + if (['knowledge_graphs_created', 'knowledge_graphs_updated', 'knowledge_graphs_deleted'].includes(msgParts[1])) { + refetchKnowledgeGraphs(); + } + }, [projectId]); + + const orgId = useSelector(selectOrganizationId); + useWebsocket(orgId, Application.REFINERY, CurrentPage.KNOWLEDGE_GRAPHS, handleWebsocketNotification, projectId); + + return (projectId &&
+
+ + + {knowledgeGraphs && knowledgeGraphs.length == 0 ? ( +
+
+

Seems like your project has no knowledge graphs yet.

+
+
+ ) : (<> +
+
+ +
+
+ )} +
+
) +} \ No newline at end of file diff --git a/src/components/shared/sidebar/Sidebar.tsx b/src/components/shared/sidebar/Sidebar.tsx index 89adc6fb..ddafc887 100644 --- a/src/components/shared/sidebar/Sidebar.tsx +++ b/src/components/shared/sidebar/Sidebar.tsx @@ -14,7 +14,7 @@ import { CacheEnum, selectCachedValue } from '@/src/reduxStore/states/cachedValu import VersionOverviewModal from './VersionOverviewModal'; import { setProjectIdSampleProject } from '@/src/reduxStore/states/tmp'; import { getHasUpdates } from '@/src/services/base/misc'; -import { MemoIconAlertCircle, MemoIconBulb, MemoIconChartPie, MemoIconMaximize, MemoIconMinimize, MemoIconSettings, MemoIconTag, MemoIconTriangleSquareCircle } from '@/submodules/react-components/components/kern-icons/icons'; +import { MemoIconAlertCircle, MemoIconBulb, MemoIconChartPie, MemoIconMaximize, MemoIconMinimize, MemoIconSettings, MemoIconSitemapFilled, MemoIconTag, MemoIconTriangleSquareCircle } from '@/submodules/react-components/components/kern-icons/icons'; export default function Sidebar() { const router = useRouter(); @@ -93,7 +93,7 @@ export default function Sidebar() { {user.role === UserRole.ENGINEER &&
-
+
{ e.preventDefault(); router.push(`/projects/${project.id}/overview`) }} className={`${project.numDataScaleUploaded == 0 ? 'opacity-50 cursor-not-allowed' : 'opacity-100 cursor-pointer'} circle ${routeColor.overview.active ? 'text-kernpurple' : 'text-white'}`}> @@ -104,7 +104,7 @@ export default function Sidebar() { {user.role === UserRole.ENGINEER &&
-
+ }
-
+
{ e.preventDefault(); router.push(`/projects/${project.id}/labeling`) }} className={`${project.numDataScaleUploaded == 0 ? 'opacity-50 cursor-not-allowed' : 'opacity-100 cursor-pointer'} circle ${routeColor.labeling.active ? 'text-kernpurple' : 'text-white'}`}> @@ -125,7 +125,7 @@ export default function Sidebar() { {user.role === UserRole.ENGINEER &&
-
+
{ e.preventDefault(); router.push(`/projects/${project.id}/heuristics`) }} className={`${project.numDataScaleUploaded == 0 ? 'opacity-50 cursor-not-allowed' : 'opacity-100 cursor-pointer'} circle ${routeColor.heuristics.active ? 'text-kernpurple' : 'text-white'}`}> @@ -136,7 +136,7 @@ export default function Sidebar() { {user.role === UserRole.ENGINEER &&
- } + {user.role === UserRole.ENGINEER && } {user.role == UserRole.ENGINEER &&
{ + dispatch(setCurrentPage(CurrentPage.KNOWLEDGE_GRAPHS)); + dispatch(setDisplayIconComments(false)); + }, []); + + return ( + + ) +} \ No newline at end of file diff --git a/src/reduxStore/states/modal.ts b/src/reduxStore/states/modal.ts index 5d76bccc..84aa5f6a 100644 --- a/src/reduxStore/states/modal.ts +++ b/src/reduxStore/states/modal.ts @@ -227,6 +227,9 @@ const initialState: Modals = { [ModalEnum.DELETE_EVALUATION_RUN]: { open: false }, + [ModalEnum.DELETE_KNOWLEDGE_GRAPH]: { + open: false + }, }; const modalSlice = createSlice({ diff --git a/src/reduxStore/states/pages/knowledge-graphs.ts b/src/reduxStore/states/pages/knowledge-graphs.ts new file mode 100644 index 00000000..c10343ce --- /dev/null +++ b/src/reduxStore/states/pages/knowledge-graphs.ts @@ -0,0 +1,51 @@ +import { KnowledgeGraph, KnowledgeGraphType } from '@/src/types/components/projects/projectId/knowledge-graphs/knowledge-graphs'; +import { InformationSourceType } from '@/submodules/javascript-functions/enums/enums'; +import { createSlice } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' + +type KnowledgeGraphsState = { + all: KnowledgeGraph[]; + active: KnowledgeGraph | null; + type: KnowledgeGraphType; +} + +function getInitState(): KnowledgeGraphsState { + return { + all: [], + active: null, + type: null, + }; +} + +function changeAllFor(obj: any, changes: { [key: string]: any }) { + for (const key in changes) obj[key] = changes[key]; +} + +const initialState = getInitState(); + +const knowledgeGraphsSlice = createSlice({ + name: 'knowledgeGraphs', + initialState, + reducers: { + setActiveKnowledgeGraph(state, action: PayloadAction) { + if (action.payload) state.active = { ...action.payload }; + else state.active = null; + }, + setAllKnowledgeGraphs(state, action: PayloadAction) { + if (action.payload) state.all = action.payload; + else state.all = []; + }, + setKnowledgeGraphType(state, action: PayloadAction) { + state.type = action.payload; + } + }, +}) + + +//selectors +export const selectKnowledgeGraph = (state) => state.knowledgeGraphs.active; +export const selectKnowledgeGraphsAll = (state) => state.knowledgeGraphs.all; +export const selectKnowledgeGraphType = (state) => state.knowledgeGraphs.type; + +export const { setAllKnowledgeGraphs, setKnowledgeGraphType, setActiveKnowledgeGraph } = knowledgeGraphsSlice.actions; +export const knowledgeGraphsReducer = knowledgeGraphsSlice.reducer; \ No newline at end of file diff --git a/src/reduxStore/store.ts b/src/reduxStore/store.ts index 77cf8505..8c4641c0 100644 --- a/src/reduxStore/store.ts +++ b/src/reduxStore/store.ts @@ -11,6 +11,7 @@ import { dataBrowserReducer } from './states/pages/data-browser'; import { cacheReducer } from './states/cachedValues'; import { labelingReducer } from './states/pages/labeling'; import { tmpReducer } from './states/tmp'; +import { knowledgeGraphsReducer } from './states/pages/knowledge-graphs'; const store = configureStore({ reducer: { @@ -25,7 +26,8 @@ const store = configureStore({ dataBrowser: dataBrowserReducer, cache: cacheReducer, labeling: labelingReducer, - tmp: tmpReducer + tmp: tmpReducer, + knowledgeGraphs: knowledgeGraphsReducer, }, devTools: process.env.IS_DEV == '1', }); diff --git a/src/services/base/knowledge-graphs.ts b/src/services/base/knowledge-graphs.ts new file mode 100644 index 00000000..135daf61 --- /dev/null +++ b/src/services/base/knowledge-graphs.ts @@ -0,0 +1,14 @@ +import { FetchType, jsonFetchWrapper } from "@/submodules/javascript-functions/basic-fetch"; +import { BACKEND_BASE_URI } from "./_settings"; + +export const knowledgeGraphEndpoint = `${BACKEND_BASE_URI}/api/v1/knowledge-graph`; + +export function deleteKnowledgeGraphById(projectId: string, knowledgeGraphId: string, onResult: (result: any) => void) { + const finalUrl = `${knowledgeGraphEndpoint}/${projectId}/${knowledgeGraphId}/delete-knowledge-graph`; + jsonFetchWrapper(finalUrl, FetchType.DELETE, onResult); +} + +export function getKnowledgeGraphs(projectId: string, onResult: (result: any) => void) { + const finalUrl = `${knowledgeGraphEndpoint}/${projectId}/knowledge-graphs`; + jsonFetchWrapper(finalUrl, FetchType.GET, onResult); +} diff --git a/src/services/base/route-manager.ts b/src/services/base/route-manager.ts index a187e2d9..c9e1cbda 100644 --- a/src/services/base/route-manager.ts +++ b/src/services/base/route-manager.ts @@ -11,6 +11,7 @@ export class RouteManager { labeling: { active: false, checkFor: ['labeling'] }, heuristics: { active: false, checkFor: ['heuristics', 'lookup-lists', 'labeling-function', 'active-learning'] }, settings: { active: false, checkFor: ['settings', 'attributes', 'upload-records', 'playground'] }, + knowledgeGraphs: { active: false, checkFor: ['knowledge-graphs'] }, admin: { active: false, checkFor: ['admin'] }, } diff --git a/src/types/components/projects/projectId/knowledge-graphs/knowledge-graphs.ts b/src/types/components/projects/projectId/knowledge-graphs/knowledge-graphs.ts new file mode 100644 index 00000000..317610b5 --- /dev/null +++ b/src/types/components/projects/projectId/knowledge-graphs/knowledge-graphs.ts @@ -0,0 +1,22 @@ +export type KnowledgeGraph = { + id: string; + name: string; + description: string; + type: KnowledgeGraphType; +}; + + +export type DeleteKnowledgeGraphModalProps = { + countSelected: number; + selectionList: string; + refetch: () => void; +}; + +export type KnowledgeGraphsHeaderProps = { + refetch: () => void; +} + +export enum KnowledgeGraphType { + LIVE = 'LIVE', + STABLE = 'STABLE', +} \ No newline at end of file diff --git a/src/types/shared/modal.ts b/src/types/shared/modal.ts index f4308271..9b24a879 100644 --- a/src/types/shared/modal.ts +++ b/src/types/shared/modal.ts @@ -72,4 +72,5 @@ export enum ModalEnum { EVALUATION_META_FILTER_APPLY = "EVALUATION_META_FILTER_APPLY", EVALUATION_REFORMULATE = "EVALUATION_REFORMULATE", DELETE_EVALUATION_RUN = "DELETE_EVALUATION_RUN", + DELETE_KNOWLEDGE_GRAPH = "DELETE_KNOWLEDGE_GRAPH", } \ No newline at end of file diff --git a/src/util/tooltip-constants.ts b/src/util/tooltip-constants.ts index 9e9ab566..a93f207c 100644 --- a/src/util/tooltip-constants.ts +++ b/src/util/tooltip-constants.ts @@ -34,6 +34,7 @@ export const TOOLTIPS_DICT = { 'LABELING': 'Labeling', 'HEURISTICS': 'Heuristics', 'SETTINGS': 'Settings', + 'KNOWLEDGE_GRAPHS': 'Knowledge graphs', 'ADMIN': 'Admin', 'DOCUMENTATION': 'Documentation', 'API': 'API', diff --git a/submodules/react-components b/submodules/react-components index 0b8008e5..f83cd0f7 160000 --- a/submodules/react-components +++ b/submodules/react-components @@ -1 +1 @@ -Subproject commit 0b8008e511d09216fa66cf565a0c0a3595ae7c53 +Subproject commit f83cd0f7855098e6a16fa4fec686756ad1d9c636