diff --git a/docs/api/public/cmf.md b/docs/api/public/cmf.md index 48954594..a6849806 100644 --- a/docs/api/public/cmf.md +++ b/docs/api/public/cmf.md @@ -8,18 +8,12 @@ members: - __init__ - create_context - - merge_created_context - create_execution - update_execution - - merge_created__execution - log_dataset - - log_dataset_with_version - log_model - - log_model_with_version - - log_execution_metrics_from_client - log_execution_metrics - log_metric - - commit_existing_metrics - create_dataslice - update_dataslice diff --git a/ui/public/index.html b/ui/public/index.html index d0acc4ae..3719ef6f 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -52,7 +52,7 @@ -
+
↓ + } else if (sortOrder === "asc"){ + return //data is in asc order ----> ↑ + } else{ + return //data is in initial order -----------> ↕ + } + } return ( -
-
- -
+
@@ -102,8 +105,7 @@ const ArtifactTable = ({ artifacts, ArtifactType, onSort, onFilter }) => { onClick={handleSort} className="name px-6 py-3" > - name {sortOrder === "asc" && } - {sortOrder === "desc" && } + name  {renderArrow()} {ArtifactType === "Model" && ( - {artifacts.length > 0 && artifacts.map((data, index) => ( + {sortedData.length > 0 && sortedData.map((data, index) => ( - @@ -190,4 +192,4 @@ const ArtifactTable = ({ artifacts, ArtifactType, onSort, onFilter }) => { ); }; -export default ArtifactTable; +export default ArtifactTable; \ No newline at end of file diff --git a/ui/src/components/ExecutionTable/index.jsx b/ui/src/components/ExecutionTable/index.jsx index 13ed976d..b3e26024 100644 --- a/ui/src/components/ExecutionTable/index.jsx +++ b/ui/src/components/ExecutionTable/index.jsx @@ -19,27 +19,34 @@ import React, { useState, useEffect } from "react"; import "./index.css"; -const ExecutionTable = ({ executions, onSort, onFilter }) => { - - // Default sorting order - const [sortOrder, setSortOrder] = useState("Context_Type"); +const ExecutionTable = ({ executions, onSort, onFilter}) => { +// Default sorting order + const [sortOrder, setSortOrder] = useState(onSort); + const [sortedData, setSortedData] = useState([]); // Local filter value state const [filterValue, setFilterValue] = useState(""); - const [expandedRow, setExpandedRow] = useState(null); const consistentColumns = []; useEffect(() => { // Set initial sorting order when component mounts - setSortOrder("asc"); - }, []); + setSortedData([...executions]); + }, [executions]); + const handleSort = () => { - const newSortOrder = sortOrder === "asc" ? "desc" : "asc"; + const newSortOrder = sortOrder === "desc" ? "asc" : sortOrder === "asc" ? "desc" : "asc"; setSortOrder(newSortOrder); - onSort("Context_Type", newSortOrder); // Notify parent component about sorting change + const sorted = [...executions].sort((a, b) => { + if(newSortOrder === "asc"){ + return a.Context_Type.localeCompare(b.Context_Type); + }else{ + return b.Context_Type.localeCompare(a.Context_Type); + } + }); + setSortedData(sorted); // Notify parent component about sorting change }; const handleFilterChange = (event) => { @@ -56,13 +63,24 @@ const ExecutionTable = ({ executions, onSort, onFilter }) => { } }; + const renderArrow = () => { + if (sortOrder === "desc"){ + return //data is in desc order ---> ↓ + } else if (sortOrder === "asc"){ + return //data is in asc order ----> ↑ + } else{ + return //data is in initial order -----------> ↕ + } + } + return (
{ onClick={handleSort} className="px-6 py-3 Context_Type" > - Context_Type {sortOrder === "asc" && } - {sortOrder === "desc" && } + Context_Type  {renderArrow()}
- {executions.map((data, index) => ( + {sortedData.map((data, index) => ( toggleRow(index)} className="text-sm font-medium text-gray-800" > - @@ -160,4 +177,4 @@ const ExecutionTable = ({ executions, onSort, onFilter }) => { ); }; -export default ExecutionTable; +export default ExecutionTable; \ No newline at end of file diff --git a/ui/src/components/ExecutionTree/index.jsx b/ui/src/components/ExecutionTree/index.jsx index 1ed8cb50..bc670ed1 100644 --- a/ui/src/components/ExecutionTree/index.jsx +++ b/ui/src/components/ExecutionTree/index.jsx @@ -116,7 +116,6 @@ const constructTangleLayout = (levels, options = {}) => { l.xt = l.target.x; l.yt = l.target.y + l.target.bundles_index[l.bundle.id].i * metro_d - (l.target.bundles.length * metro_d) / 2 + metro_d / 2; l.xb = l.bundle.x; - l.yb = l.bundle.y; l.xs = l.source.x; l.ys = l.source.y; }); @@ -130,7 +129,7 @@ const constructTangleLayout = (levels, options = {}) => { links.forEach(l => { l.yt = l.target.y + l.target.bundles_index[l.bundle.id].i * metro_d - (l.target.bundles.length * metro_d) / 2 + metro_d / 2; l.ys = l.source.y; - l.c1 = l.source.level - l.target.level > 1 ? Math.min(options.bigc, l.xb - l.xt, l.yb - l.yt) - c : c; + l.c1 = c; l.c2 = c; }); diff --git a/ui/src/components/Loader/index.css b/ui/src/components/Loader/index.css new file mode 100644 index 00000000..e65852b7 --- /dev/null +++ b/ui/src/components/Loader/index.css @@ -0,0 +1,29 @@ +.loader-container { + position: relative; /* Relative positioning for the loader to be placed absolutely inside */ + width: 100%; + height: 100%; /* Ensure it takes the full height of its parent */ +} + +/* Loader is absolutely positioned within the loader-container */ +.loader { + position: absolute; + top: 100%; + left: 50%; + transform: translate(-50%, -50%); /* Center the loader */ + border: 8px solid #f3f3f3; + border-top: 8px solid #3498db; + border-radius: 50%; + width: 100px; /* Size of the loader */ + height: 100px; /* Size of the loader */ + animation: spin 1.5s linear infinite; + z-index: 9999; /* Ensure it appears on top */ +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} diff --git a/ui/src/components/Loader/index.jsx b/ui/src/components/Loader/index.jsx index 0c224b50..105c6bcb 100644 --- a/ui/src/components/Loader/index.jsx +++ b/ui/src/components/Loader/index.jsx @@ -15,19 +15,14 @@ ***/ -import React from "react"; +import React from 'react'; +import './index.css'; -function Loader() { - return ( -
- Test data -
- ); -} +const Loader = () => ( +
+
+
+); export default Loader; + diff --git a/ui/src/components/TangledTree/index.jsx b/ui/src/components/TangledTree/index.jsx index b1bce70a..a03fe489 100644 --- a/ui/src/components/TangledTree/index.jsx +++ b/ui/src/components/TangledTree/index.jsx @@ -112,13 +112,12 @@ const constructTangleLayout = (levels, options = {}) => { i += l.length; }); + // establish the basic structure and position links.forEach(l => { l.xt = l.target.x; l.yt = l.target.y + l.target.bundles_index[l.bundle.id].i * metro_d - (l.target.bundles.length * metro_d) / 2 + metro_d / 2; l.xb = l.bundle.x; - l.yb = l.bundle.y; l.xs = l.source.x; - l.ys = l.source.y; }); var y_negative_offset = 0; @@ -127,10 +126,11 @@ const constructTangleLayout = (levels, options = {}) => { l.forEach(n => (n.y -= y_negative_offset)); }); + // Fine tune the visual appearance of link (how the links need to display in svg) links.forEach(l => { l.yt = l.target.y + l.target.bundles_index[l.bundle.id].i * metro_d - (l.target.bundles.length * metro_d) / 2 + metro_d / 2; l.ys = l.source.y; - l.c1 = l.source.level - l.target.level > 1 ? Math.min(options.bigc, l.xb - l.xt, l.yb - l.yt) - c : c; + l.c1 = c; l.c2 = c; }); @@ -151,10 +151,12 @@ const renderChart = (data, options = {}) => { options.background_color ||= 'white'; // Default background color const tangleLayout = constructTangleLayout(_.cloneDeep(data), options); + tangled_width = tangleLayout.layout.width; tangled_height = tangleLayout.layout.height; const textPadding = 12; const labelOffset = 4; + const svg_width = (tangled_width + textPadding * 10) < 1000 ? `${tangled_width + textPadding * 50}` : `${tangled_width + textPadding * 10}`; return ( <> - + {tangleLayout.bundles.map((b, i) => { let d = b.links .map( diff --git a/ui/src/pages/artifacts/ArtifactTypeSidebar.jsx b/ui/src/pages/artifacts/ArtifactTypeSidebar.jsx index c931ad05..55b4c32f 100644 --- a/ui/src/pages/artifacts/ArtifactTypeSidebar.jsx +++ b/ui/src/pages/artifacts/ArtifactTypeSidebar.jsx @@ -19,9 +19,13 @@ import React, { useState, useEffect } from "react"; import "./index.css"; -const ArtifactTypeSidebar = ({ artifactTypes, handleArtifactTypeClick }) => { +const ArtifactTypeSidebar = ({ artifactTypes, handleArtifactTypeClick, onFilter}) => { const [clickedArtifactType, setClickedArtifactType] = useState(artifactTypes[0]); + + // Local filter value state + const [filterValue, setFilterValue] = useState(""); + useEffect(() => { handleClick(artifactTypes[0]); // eslint-disable-next-line @@ -35,9 +39,17 @@ const ArtifactTypeSidebar = ({ artifactTypes, handleArtifactTypeClick }) => { handleArtifactTypeClick(artifactType); }; + const handleFilterChange = (event) => { + const value = event.target.value; + setFilterValue(value); + onFilter("name", value); // Notify parent component about filter change + }; + + return ( + <>
-
+
{artifactTypes.map((artifactType, index) => ( ))} -
+
+
+
+ +
+
+ ); }; -export default ArtifactTypeSidebar; +export default ArtifactTypeSidebar; \ No newline at end of file diff --git a/ui/src/pages/artifacts/index.jsx b/ui/src/pages/artifacts/index.jsx index 3288b2b1..ee1ddd04 100644 --- a/ui/src/pages/artifacts/index.jsx +++ b/ui/src/pages/artifacts/index.jsx @@ -24,6 +24,7 @@ import Footer from "../../components/Footer"; import "./index.css"; import Sidebar from "../../components/Sidebar"; import ArtifactTypeSidebar from "./ArtifactTypeSidebar"; +import Loader from "../../components/Loader"; const client = new FastAPIClient(config); @@ -41,16 +42,21 @@ const Artifacts = () => { // Default sort field const [sortField, setSortField] = useState("name"); // Default sort order - const [sortOrder, setSortOrder] = useState("asc"); + const [sortOrder, setSortOrder] = useState(null); // Default filter field const [filterBy, setFilterBy] = useState(null); // Default filter value const [filterValue, setFilterValue] = useState(null); + // Default loader value + const [loading, setLoading] = useState(true); + const fetchPipelines = () => { + setLoading(true); client.getPipelines("").then((data) => { setPipelines(data); setSelectedPipeline(data[0]); + setLoading(false); }); }; @@ -76,8 +82,10 @@ const Artifacts = () => { }; const fetchArtifactTypes = () => { + setLoading(true); client.getArtifactTypes().then((types) => { setArtifactTypes(types); + setLoading(false) fetchArtifacts(selectedPipeline, types[0], activePage, sortField, sortOrder, filterBy, filterValue); }); }; @@ -92,9 +100,11 @@ const Artifacts = () => { const fetchArtifacts = (pipelineName, type, page, sortField, sortOrder, filterBy, filterValue) => { setArtifacts(undefined) // if data then set artifacts with that data else set it null. + setLoading(true); client.getArtifacts(pipelineName, type, page, sortField, sortOrder, filterBy, filterValue).then((data) => { setArtifacts(data.items); setTotalItems(data.total_items); + setLoading(false); }) .catch(() => setArtifacts(null)); }; @@ -149,109 +159,104 @@ const Artifacts = () => { pipelines={pipelines} handlePipelineClick={handlePipelineClick} /> -
+
{selectedPipeline !== null && ( )}
-
- {selectedPipeline !== null && selectedArtifactType !== null ? ( artifacts === undefined ? ( -
Loading....
- ): artifacts === null || artifacts.length === 0 ? ( -
No Data
- ):( - <> - - - {totalItems > 0 && ( -
- - {Array.from({ length: Math.ceil(totalItems / 5) }).map( - (_, index) => { - const pageNumber = index + 1; - if ( - pageNumber === 1 || - pageNumber === Math.ceil(totalItems / 5) - ) { - return ( - - ); - } else if ( - (activePage <= 3 && pageNumber <= 6) || - (activePage >= Math.ceil(totalItems / 5) - 2 && - pageNumber >= Math.ceil(totalItems / 5) - 5) || - Math.abs(pageNumber - activePage) <= 2 - ) { - return ( - - ); - } else if ( - (pageNumber === 2 && activePage > 3) || - (pageNumber === Math.ceil(totalItems / 5) - 1 && - activePage < Math.ceil(totalItems / 5) - 3) - ) { - return ( - - ... - - ); + { loading? ():( +
+ {selectedPipeline !== null && selectedArtifactType !== null && artifacts !== null && artifacts !== {} && ( + + )} + {artifacts !== null && totalItems > 0 && ( + <> + + {Array.from({ length: Math.ceil(totalItems / 5) }).map( + (_, index) => { + const pageNumber = index + 1; + if ( + pageNumber === 1 || + pageNumber === Math.ceil(totalItems / 5) + ) { + return ( + + ); + } else if ( + (activePage <= 3 && pageNumber <= 6) || + (activePage >= Math.ceil(totalItems / 5) - 2 && + pageNumber >= Math.ceil(totalItems / 5) - 5) || + Math.abs(pageNumber - activePage) <= 2 + ) { + return ( + + ); + } else if ( + (pageNumber === 2 && activePage > 3) || + (pageNumber === Math.ceil(totalItems / 5) - 1 && + activePage < Math.ceil(totalItems / 5) - 3) + ) { + return ( + + ... + + ); + } + return null; } - return null; - } - )} - -
- )} - - ) - ): null} -
-
+ )} + + + )} +
+ )} +
); }; -export default Artifacts; +export default Artifacts; \ No newline at end of file diff --git a/ui/src/pages/executions/index.jsx b/ui/src/pages/executions/index.jsx index bca6a7be..fe0bd727 100644 --- a/ui/src/pages/executions/index.jsx +++ b/ui/src/pages/executions/index.jsx @@ -23,6 +23,7 @@ import ExecutionTable from "../../components/ExecutionTable"; import Footer from "../../components/Footer"; import "./index.css"; import Sidebar from "../../components/Sidebar"; +import Loader from "../../components/Loader"; const client = new FastAPIClient(config); @@ -36,26 +37,29 @@ const Executions = () => { // Default sort field const [sortField, setSortField] = useState("Context_Type"); // Default sort order - const [sortOrder, setSortOrder] = useState("asc"); + const [sortOrder, setSortOrder] = useState(null); // Default filter field const [filterBy, setFilterBy] = useState(null); // Default filter value const [filterValue, setFilterValue] = useState(null); + // Default value for loader + const [loading, setLoading] = useState(true); useEffect(() => { fetchPipelines(); }, []); const fetchPipelines = () => { + setLoading(true); client.getPipelines("").then((data) => { setPipelines(data); setSelectedPipeline(data[0]); + setLoading(false); }); }; useEffect(() => { if (selectedPipeline) { - setExecutions(null); fetchExecutions(selectedPipeline, activePage, sortField, sortOrder, filterBy, filterValue); } }, [selectedPipeline, activePage, sortField, sortOrder, filterBy, filterValue]); @@ -96,6 +100,7 @@ const Executions = () => { const handleSort = (newSortField, newSortOrder) => { setSortField(newSortField); setSortOrder(newSortOrder); + fetchExecutions(selectedPipeline, activePage, newSortField, newSortOrder, filterBy, filterValue); }; const handleFilter = (field, value) => { @@ -115,90 +120,92 @@ const Executions = () => { pipelines={pipelines} handlePipelineClick={handlePipelineClick} /> -
-
- {selectedPipeline !== null && executions !== null && ( - - )} -
-
- {executions !== null && totalItems > 0 && ( - <> - - {Array.from({ length: Math.ceil(totalItems / 5) }).map( - (_, index) => { - const pageNumber = index + 1; - if ( - pageNumber === 1 || - pageNumber === Math.ceil(totalItems / 5) - ) { - return ( - - ); - } else if ( - (activePage <= 3 && pageNumber <= 6) || - (activePage >= Math.ceil(totalItems / 5) - 2 && - pageNumber >= Math.ceil(totalItems / 5) - 5) || - Math.abs(pageNumber - activePage) <= 2 - ) { - return ( - - ); - } else if ( - (pageNumber === 2 && activePage > 3) || - (pageNumber === Math.ceil(totalItems / 5) - 1 && - activePage < Math.ceil(totalItems / 5) - 3) - ) { - return ( - - ... - - ); - } - return null; - } +
+ {loading ? ():( +
+ {selectedPipeline !== null && executions !== null && ( + + )} +
+ {executions !== null && totalItems > 0 && ( + <> + + {Array.from({ length: Math.ceil(totalItems / 5) }).map( + (_, index) => { + const pageNumber = index + 1; + if ( + pageNumber === 1 || + pageNumber === Math.ceil(totalItems / 5) + ) { + return ( + + ); + } else if ( + (activePage <= 3 && pageNumber <= 6) || + (activePage >= Math.ceil(totalItems / 5) - 2 && + pageNumber >= Math.ceil(totalItems / 5) - 5) || + Math.abs(pageNumber - activePage) <= 2 + ) { + return ( + + ); + } else if ( + (pageNumber === 2 && activePage > 3) || + (pageNumber === Math.ceil(totalItems / 5) - 1 && + activePage < Math.ceil(totalItems / 5) - 3) + ) { + return ( + + ... + + ); + } + return null; + } + )} + + )} - - - )} -
+
+
+ )}
@@ -207,4 +214,4 @@ const Executions = () => { ); }; -export default Executions; +export default Executions; \ No newline at end of file diff --git a/ui/src/pages/lineage/index.jsx b/ui/src/pages/lineage/index.jsx index 6f6f6075..9454af40 100644 --- a/ui/src/pages/lineage/index.jsx +++ b/ui/src/pages/lineage/index.jsx @@ -27,20 +27,22 @@ import TangledTree from "../../components/TangledTree"; import ExecutionDropdown from "../../components/ExecutionDropdown"; import ExecutionTree from "../../components/ExecutionTree"; import ExecutionTangledDropdown from "../../components/ExecutionTangledDropdown"; +import Loader from "../../components/Loader"; const client = new FastAPIClient(config); const Lineage = () => { const [pipelines, setPipelines] = useState([]); const [selectedPipeline, setSelectedPipeline] = useState(null); - const LineageTypes=['Artifact_Tree', 'Execution_Tree']; + const LineageTypes = ['Artifact_Tree', 'Execution_Tree']; const [selectedLineageType, setSelectedLineageType] = useState('Artifact_Tree'); const [selectedExecutionType, setSelectedExecutionType] = useState(null); - const [lineageData, setLineageData]=useState(null); - const [executionData, setExecutionData]=useState(null); + const [lineageData, setLineageData] = useState(null); + const [executionData, setExecutionData] = useState(null); const [lineageArtifactsKey, setLineageArtifactsKey] = useState(0); const [execDropdownData,setExecDropdownData] = useState([]); - const [artitreeData, setArtiTreeData]=useState(null); + const [artitreeData, setArtiTreeData] = useState(null); + const [loading, setLoading] = useState(true); // fetching list of pipelines useEffect(() => { @@ -48,6 +50,7 @@ const Lineage = () => { }, []); const fetchPipelines = () => { + setLoading(true); client.getPipelines("").then((data) => { setPipelines(data); setSelectedPipeline(data[0]); @@ -59,6 +62,7 @@ const Lineage = () => { // call artifact lineage as it is default fetchArtifactTree(data[0]); } + setLoading(false); }); }; const handlePipelineClick = (pipeline) => { @@ -101,25 +105,30 @@ const Lineage = () => { const fetchArtifactLineage = (pipelineName) => { + setLoading(true); client.getArtifactLineage(pipelineName).then((data) => { if (data === null) { setLineageData(null); } setLineageData(data); + setLoading(false); }); setLineageArtifactsKey((prevKey) => prevKey + 1); }; const fetchArtifactTree = (pipelineName) => { + setLoading(true); client.getArtiTreeLineage(pipelineName).then((data) => { - if (data === null) { - setArtiTreeData(null); - } - setArtiTreeData(data); + if (data === null) { + setArtiTreeData(null); + } + setArtiTreeData(data); + setLoading(false); }); }; const fetchExecutionTypes = (pipelineName, lineageType) => { + setLoading(true); client.getExecutionTypes(pipelineName).then((data) => { if (data === null ) { setExecDropdownData(null); @@ -136,7 +145,7 @@ const Lineage = () => { fetchExecTree(pipelineName,uuid); } } - + setLoading(false); }); setLineageArtifactsKey((prevKey) => prevKey + 1); }; @@ -159,17 +168,21 @@ const Lineage = () => { }; const fetchExecutionLineage = (pipelineName,uuid) => { + setLoading(true); client.getExecutionLineage(pipelineName,uuid).then((data) => { if (data === null) { setExecutionData(null); } setExecutionData(data); + setLoading(false); }); }; const fetchExecTree = (pipelineName,exec_type) => { + setLoading(true); client.getExecTreeLineage(pipelineName,exec_type).then((data) => { - setExecutionData(data); + setExecutionData(data); + setLoading(false); }); }; @@ -197,35 +210,35 @@ const Lineage = () => { )}
- {selectedPipeline !== null && selectedLineageType === "Artifacts" && lineageData !== null && ( - + { loading && } + {!loading && selectedPipeline !== null && selectedLineageType === "Artifacts" && lineageData !== null && ( + )} - {selectedPipeline !== null && selectedLineageType === "Execution" && execDropdownData !== null && executionData !== null &&( + {!loading && selectedPipeline !== null && selectedLineageType === "Execution" && execDropdownData !== null && executionData !== null &&(
- +
- )} - {selectedPipeline !== null && selectedLineageType === "Execution" && execDropdownData !== null && executionData !== null &&( + )} + {!loading && selectedPipeline !== null && selectedLineageType === "Execution" && execDropdownData !== null && executionData !== null &&(
- +
- )} - {selectedPipeline !== null && selectedLineageType === "Execution_Tree" && execDropdownData !== null &&( + )} + {!loading && selectedPipeline !== null && selectedLineageType === "Execution_Tree" && execDropdownData !== null &&(
- +
- )} - {selectedPipeline !== null && selectedLineageType === "Execution_Tree" && execDropdownData !== null && executionData !== null &&( + )} + {!loading && selectedPipeline !== null && selectedLineageType === "Execution_Tree" && execDropdownData !== null && executionData !== null &&(
- +
- )} - {selectedPipeline !== null && selectedLineageType === "Artifact_Tree" && artitreeData !== null &&( + )} + {!loading && selectedPipeline !== null && selectedLineageType === "Artifact_Tree" && artitreeData !== null &&(
- +
- )} - + )}
@@ -236,4 +249,4 @@ const Lineage = () => { ); }; -export default Lineage; +export default Lineage; \ No newline at end of file
@@ -129,13 +131,13 @@ const ArtifactTable = ({ artifacts, ArtifactType, onSort, onFilter }) => {
toggleRow(index)}> + toggleRow(index)}> {expandedRow === index ? "-" : "+"} {data.id} Execution @@ -106,14 +123,14 @@ const ExecutionTable = ({ executions, onSort, onFilter }) => {
+ {expandedRow === index ? "-" : "+"} {data.Context_Type}