diff --git a/cmflib/cmf.py b/cmflib/cmf.py
index 0cf26e48..6df08508 100644
--- a/cmflib/cmf.py
+++ b/cmflib/cmf.py
@@ -1293,17 +1293,30 @@ def commit_metrics(self, metrics_name: str):
def commit_existing_metrics(self, metrics_name: str, uri: str, custom_properties: t.Optional[t.Dict] = None):
custom_props = {} if custom_properties is None else custom_properties
- metrics = create_new_artifact_event_and_attribution(
- store=self.store,
- execution_id=self.execution.id,
- context_id=self.child_context.id,
- uri=uri,
- name=metrics_name,
- type_name="Step_Metrics",
- event_type=mlpb.Event.Type.OUTPUT,
- custom_properties=custom_props,
- milliseconds_since_epoch=int(time.time() * 1000),
- )
+ c_hash = uri.strip()
+ existing_artifact = []
+ existing_artifact.extend(self.store.get_artifacts_by_uri(c_hash))
+ if (existing_artifact
+ and len(existing_artifact) != 0 ):
+ metrics = link_execution_to_artifact(
+ store=self.store,
+ execution_id=self.execution.id,
+ uri=c_hash,
+ input_name=metrics_name,
+ event_type=mlpb.Event.Type.OUTPUT,
+ )
+ else:
+ metrics = create_new_artifact_event_and_attribution(
+ store=self.store,
+ execution_id=self.execution.id,
+ context_id=self.child_context.id,
+ uri=uri,
+ name=metrics_name,
+ type_name="Step_Metrics",
+ event_type=mlpb.Event.Type.OUTPUT,
+ custom_properties=custom_props,
+ milliseconds_since_epoch=int(time.time() * 1000),
+ )
if self.graph:
self.driver.create_metrics_node(
metrics_name,
diff --git a/cmflib/cmfquery.py b/cmflib/cmfquery.py
index 00d634fc..e87769b6 100644
--- a/cmflib/cmfquery.py
+++ b/cmflib/cmfquery.py
@@ -389,9 +389,63 @@ def get_all_exe_in_stage(self, stage_name: str) -> t.List[mlpb.Execution]:
return self.store.get_executions_by_context(stage.id)
return []
+ def get_all_executions_by_ids_list(self, exe_ids: t.List[int]) -> pd.DataFrame:
+ """Return executions for given execution ids list as a pandas data frame.
+
+ Args:
+ exe_ids: List of execution identifiers.
+
+ Returns:
+ Data frame with all executions for the list of given execution identifiers.
+ """
+
+ df = pd.DataFrame()
+ executions = self.store.get_executions_by_id(exe_ids)
+ for exe in executions:
+ d1 = self._transform_to_dataframe(exe)
+ df = pd.concat([df, d1], sort=True, ignore_index=True)
+ return df
+
+ def get_all_artifacts_by_context(self, pipeline_name: str) -> pd.DataFrame:
+ """Return artifacts for given pipeline name as a pandas data frame.
+
+ Args:
+ pipeline_name: Name of the pipeline.
+
+ Returns:
+ Data frame with all artifacts associated with given pipeline name.
+ """
+ df = pd.DataFrame()
+ contexts = self.store.get_contexts_by_type("Parent_Context")
+ context_id = self.get_pipeline_id(pipeline_name)
+ for ctx in contexts:
+ if ctx.id == context_id:
+ child_contexts = self.store.get_children_contexts_by_context(ctx.id)
+ for cc in child_contexts:
+ artifacts = self.store.get_artifacts_by_context(cc.id)
+ for art in artifacts:
+ d1 = self.get_artifact_df(art)
+ df = pd.concat([df, d1], sort=True, ignore_index=True)
+ return df
+
+ def get_all_artifacts_by_ids_list(self, artifact_ids: t.List[int]) -> pd.DataFrame:
+ """Return all artifacts for the given artifact ids list.
+
+ Args:
+ artifact_ids: List of artifact identifiers
+
+ Returns:
+ Data frame with all artifacts for the given artifact ids list.
+ """
+ df = pd.DataFrame()
+ artifacts = self.store.get_artifacts_by_id(artifact_ids)
+ for art in artifacts:
+ d1 = self.get_artifact_df(art)
+ df = pd.concat([df, d1], sort=True, ignore_index=True)
+ return df
+
def get_all_executions_in_stage(self, stage_name: str) -> pd.DataFrame:
"""Return executions of the given stage as pandas data frame.
-
Args:
stage_name: Stage name. See doc strings for the prev method.
Returns:
@@ -471,6 +525,16 @@ def get_all_artifacts_for_execution(self, execution_id: int) -> pd.DataFrame:
)
return df
+ def get_all_artifact_types(self) -> t.List[str]:
+ """Return names of all artifact types.
+
+ Returns:
+ List of all artifact types.
+ """
+ artifact_list = self.store.get_artifact_types()
+ types=[i.name for i in artifact_list]
+ return types
+
def get_all_executions_for_artifact(self, artifact_name: str) -> pd.DataFrame:
"""Return executions that consumed and produced given artifact.
@@ -491,6 +555,7 @@ def get_all_executions_for_artifact(self, artifact_name: str) -> pd.DataFrame:
"Type": "INPUT" if event.type == mlpb.Event.Type.INPUT else "OUTPUT",
"execution_id": event.execution_id,
"execution_name": self.store.get_executions_by_id([event.execution_id])[0].name,
+ "execution_type_name":self.store.get_executions_by_id([event.execution_id])[0].properties['Execution_type_name'],
"stage": stage_ctx.name,
"pipeline": self.store.get_parent_contexts_by_context(stage_ctx.id)[0].name,
}
@@ -598,6 +663,7 @@ def find_producer_execution(self, artifact_name: str) -> t.Optional[mlpb.Executi
executions_ids = set(
event.execution_id
for event in self.store.get_events_by_artifact_ids([artifact.id])
+
if event.type == mlpb.Event.OUTPUT
)
if not executions_ids:
diff --git a/cmflib/commands/metadata/push.py b/cmflib/commands/metadata/push.py
index 3f299be6..78e8fc5f 100644
--- a/cmflib/commands/metadata/push.py
+++ b/cmflib/commands/metadata/push.py
@@ -59,6 +59,8 @@ def run(self):
attr_dict = CmfConfig.read_config(config_file_path)
url = attr_dict.get("cmf-server-ip", "http://127.0.0.1:80")
+ print("metadata push started")
+ print("........................................")
if self.args.pipeline_name in query.get_pipeline_names(): # Checks if pipeline name exists
json_payload = query.dumptojson(
diff --git a/cmflib/metadata_helper.py b/cmflib/metadata_helper.py
index 095b221e..ef8436bc 100644
--- a/cmflib/metadata_helper.py
+++ b/cmflib/metadata_helper.py
@@ -305,6 +305,7 @@ def create_new_execution_in_existing_context(
EXECUTION_CONTEXT_NAME_PROPERTY_NAME = "Context_Type"
EXECUTION_CONTEXT_ID = "Context_ID"
EXECUTION_EXECUTION = "Execution"
+EXECUTION_EXECUTION_TYPE_NAME="Execution_type_name"
EXECUTION_REPO = "Git_Repo"
EXECUTION_START_COMMIT = "Git_Start_Commit"
EXECUTION_END_COMMIT = "Git_End_Commit"
@@ -402,6 +403,7 @@ def create_new_execution_in_existing_run_context(
EXECUTION_CONTEXT_NAME_PROPERTY_NAME: metadata_store_pb2.STRING,
EXECUTION_CONTEXT_ID: metadata_store_pb2.INT,
EXECUTION_EXECUTION: metadata_store_pb2.STRING,
+ EXECUTION_EXECUTION_TYPE_NAME: metadata_store_pb2.STRING,
EXECUTION_PIPELINE_TYPE: metadata_store_pb2.STRING,
EXECUTION_PIPELINE_ID: metadata_store_pb2.INT,
EXECUTION_REPO: metadata_store_pb2.STRING,
@@ -415,6 +417,7 @@ def create_new_execution_in_existing_run_context(
# Mistakenly used for grouping in the UX
EXECUTION_CONTEXT_ID: metadata_store_pb2.Value(int_value=context_id),
EXECUTION_EXECUTION: metadata_store_pb2.Value(string_value=execution),
+ EXECUTION_EXECUTION_TYPE_NAME: metadata_store_pb2.Value(string_value=execution_type_name),
EXECUTION_PIPELINE_TYPE: metadata_store_pb2.Value(string_value=pipeline_type),
EXECUTION_PIPELINE_ID: metadata_store_pb2.Value(int_value=pipeline_id),
EXECUTION_REPO: metadata_store_pb2.Value(string_value=git_repo),
diff --git a/server/app/get_data.py b/server/app/get_data.py
index 31e9ca66..42f006a8 100644
--- a/server/app/get_data.py
+++ b/server/app/get_data.py
@@ -5,49 +5,89 @@
from server.app.query_visualization import query_visualization
from fastapi.responses import FileResponse
-def get_executions(mlmdfilepath, pipeline_name):
+def get_executions_by_ids(mlmdfilepath, pipeline_name, exe_ids):
query = cmfquery.CmfQuery(mlmdfilepath)
- stages = query.get_pipeline_stages(pipeline_name)
df = pd.DataFrame()
- for stage in stages:
- executions = query.get_all_executions_in_stage(stage)
- if str(executions.Pipeline_Type[0]) == pipeline_name:
- df = pd.concat([df, executions], sort=True, ignore_index=True)
+ executions = query.get_all_executions_by_ids_list(exe_ids)
+ df = pd.concat([df, executions], sort=True, ignore_index=True)
+ #df=df.drop('name',axis=1)
return df
+def get_all_exe_ids(mlmdfilepath):
+ query = cmfquery.CmfQuery(mlmdfilepath)
+ df = pd.DataFrame()
+ execution_ids = {}
+ names = query.get_pipeline_names()
+ for name in names:
+ stages = query.get_pipeline_stages(name)
+ for stage in stages:
+ executions = query.get_all_executions_in_stage(stage)
+ df = pd.concat([df, executions], sort=True, ignore_index=True)
+ if df.empty:
+ return
+ for name in names:
+ execution_ids[name] = df.loc[df['Pipeline_Type'] == name, ['id', 'Context_Type']]
+ return execution_ids
+
+def get_all_artifact_ids(mlmdfilepath):
+ # following is a dictionary of dictionary
+ # First level dictionary key is pipeline_name
+ # First level dicitonary value is nested dictionary
+ # Nested dictionary key is type i.e. Dataset, Model, etc.
+ # Nested dictionary value is ids i.e. set of integers
+ artifact_ids = {}
+ query = cmfquery.CmfQuery(mlmdfilepath)
+ names = query.get_pipeline_names()
+ for name in names:
+ df = pd.DataFrame()
+ artifacts = query.get_all_artifacts_by_context(name)
+ df = pd.concat([df, artifacts], sort=True, ignore_index=True)
+ if df.empty:
+ return
+ else:
+ artifact_ids[name] = {}
+ for art_type in df['type']:
+ filtered_values = df.loc[df['type'] == art_type, ['id', 'name']]
+ artifact_ids[name][art_type] = filtered_values
+ return artifact_ids
-# This function fetches all the artifacts available in given mlmd
-def get_artifacts(mlmdfilepath, pipeline_name, data): # get_artifacts return value (artifact_type or artifact_df) is
- # determined by a data variable().
+def get_artifacts(mlmdfilepath, pipeline_name, art_type, artifact_ids):
query = cmfquery.CmfQuery(mlmdfilepath)
names = query.get_pipeline_names() # getting all pipeline names in mlmd
- identifiers = []
- for name in names:
- if name==pipeline_name:
- stages = query.get_pipeline_stages(name)
- for stage in stages:
- executions = query.get_all_exe_in_stage(stage)
- for exe in executions:
- identifiers.append(exe.id)
- name = []
- url = []
df = pd.DataFrame()
- for identifier in identifiers:
- get_artifacts = query.get_all_artifacts_for_execution(
- identifier
- ) # getting all artifacts
- df = pd.concat([df, get_artifacts], sort=True, ignore_index=True)
- df['event'] = df.groupby('id')['event'].transform(lambda x: ', '.join(x))
- df['name'] = df['name'].str.split(':').str[0]
- df=df.drop_duplicates()
- if data == "artifact_type":
- tempout = list(set(df["type"]))
- else:
- df = df.loc[df["type"] == data]
- result = df.to_json(orient="records")
- tempout = json.loads(result)
- return tempout
+ for name in names:
+ if name == pipeline_name:
+ df = query.get_all_artifacts_by_ids_list(artifact_ids)
+ if len(df) == 0:
+ return
+ df = df.drop_duplicates()
+ art_names = df['name'].tolist()
+ name_dict = {}
+ name_list = []
+ exec_type_name_list = []
+ exe_type_name = pd.DataFrame()
+ for name in art_names:
+ executions = query.get_all_executions_for_artifact(name)
+ exe_type_name = pd.concat([exe_type_name,executions],ignore_index=True)
+ execution_type_name = exe_type_name["execution_type_name"].drop_duplicates().tolist()
+ execution_type_name = [str(element).split('"')[1] for element in execution_type_name]
+ execution_type_name_str = ',\n '.join(map(str, execution_type_name))
+ name_list.append(name)
+ exec_type_name_list.append(execution_type_name_str)
+ name_dict['name'] = name_list
+ name_dict['execution_type_name'] = exec_type_name_list
+ name_df = pd.DataFrame(name_dict)
+ merged_df = df.merge(name_df, on='name', how='left')
+ merged_df['name'] = merged_df['name'].apply(lambda x: x.split(':')[0] if ':' in x else x)
+ merged_df = merged_df.loc[merged_df["type"] == art_type]
+ result = merged_df.to_json(orient="records")
+ tempout = json.loads(result)
+ return tempout
+def get_artifact_types(mlmdfilepath):
+ query = cmfquery.CmfQuery(mlmdfilepath)
+ artifact_types = query.get_all_artifact_types()
+ return artifact_types
def create_unique_executions(server_store_path, req_info):
mlmd_data = json.loads(req_info["json_payload"])
@@ -68,7 +108,7 @@ def create_unique_executions(server_store_path, req_info):
executions_client = []
for i in mlmd_data['Pipeline'][0]["stages"]: # checks if given execution_id present in mlmd
for j in i["executions"]:
- if j['name'] != "":#If executions have name , they are reusable executions
+ if j['name'] != "": #If executions have name , they are reusable executions
continue #which needs to be merged in irrespective of whether already
#present or not so that new artifacts associated with it gets in.
if 'Execution_uuid' in j['properties']:
diff --git a/server/app/main.py b/server/app/main.py
index f62bc124..b0f90bc7 100644
--- a/server/app/main.py
+++ b/server/app/main.py
@@ -1,23 +1,50 @@
# cmf-server api's
-from fastapi import FastAPI, Request, APIRouter, status, HTTPException
-import pandas as pd
+from fastapi import FastAPI, Request, status, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
-from cmflib import cmfquery, cmf_merger
-from fastapi.encoders import jsonable_encoder
-from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
-from server.app.get_data import get_executions, get_artifacts,get_lineage_img_path,create_unique_executions,get_mlmd_from_server
+
+from contextlib import asynccontextmanager
+import pandas as pd
+
+from cmflib import cmfquery, cmf_merger
+from server.app.get_data import (
+ get_artifacts,
+ get_lineage_img_path,
+ create_unique_executions,
+ get_mlmd_from_server,
+ get_artifact_types,
+ get_all_artifact_ids,
+ get_all_exe_ids,
+ get_executions_by_ids
+)
from server.app.query_visualization import query_visualization
-from server.app.schemas.dataframe import ExecutionDataFrame
+
from pathlib import Path
import os
import json
-app = FastAPI(title="cmf-server")
+server_store_path = "/cmf-server/data/mlmd"
+
+dict_of_art_ids = {}
+dict_of_exe_ids = {}
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ global dict_of_art_ids
+ global dict_of_exe_ids
+ if os.path.exists(server_store_path):
+ # loaded artifact ids into memory
+ dict_of_art_ids = get_all_artifact_ids(server_store_path)
+ # loaded execution ids with names into memory
+ dict_of_exe_ids = get_all_exe_ids(server_store_path)
+ yield
+ dict_of_art_ids.clear()
+ dict_of_exe_ids.clear()
+
+app = FastAPI(title="cmf-server", lifespan=lifespan)
BASE_PATH = Path(__file__).resolve().parent
-templates = Jinja2Templates(directory=str(BASE_PATH/"template"))
app.mount("/cmf-server/data/static", StaticFiles(directory="/cmf-server/data/static"), name="static")
server_store_path = "/cmf-server/data/mlmd"
if os.environ.get("MYIP") != "127.0.0.1":
@@ -39,6 +66,7 @@
allow_headers=["*"],
)
+
@app.get("/")
def read_root(request: Request):
return {"cmf-server"}
@@ -47,8 +75,13 @@ def read_root(request: Request):
# api to post mlmd file to cmf-server
@app.post("/mlmd_push")
async def mlmd_push(info: Request):
+ print("mlmd push started")
+ print("......................")
req_info = await info.json()
status= create_unique_executions(server_store_path,req_info)
+ # async function
+ await update_global_art_dict()
+ await update_global_exe_dict()
return {"status": status, "data": req_info}
@@ -64,16 +97,43 @@ async def mlmd_pull(info: Request, pipeline_name: str):
json_payload = ""
return json_payload
-
# api to display executions available in mlmd
@app.get("/display_executions/{pipeline_name}")
-async def display_exec(request: Request, pipeline_name: str):
+async def display_exec(
+ request: Request,
+ pipeline_name: str,
+ page: int = Query(1, description="Page number", gt=0),
+ per_page: int = Query(5, description="Items per page", le=100),
+ sort_field: str = Query("Context_Type", description="Column to sort by"),
+ sort_order: str = Query("asc", description="Sort order (asc or desc)"),
+ filter_by: str = Query(None, description="Filter by column"),
+ filter_value: str = Query(None, description="Filter value"),
+ ):
# checks if mlmd file exists on server
if os.path.exists(server_store_path):
- execution_df = get_executions(server_store_path, pipeline_name)
- tempOut = execution_df.to_json(orient="records")
- parsed = json.loads(tempOut)
- return parsed
+ exe_ids_initial = dict_of_exe_ids[pipeline_name]
+ # Apply filtering if provided
+ if filter_by and filter_value:
+ exe_ids_initial = exe_ids_initial[exe_ids_initial[filter_by].str.contains(filter_value, case=False)]
+ # Apply sorting if provided
+ exe_ids_sorted = exe_ids_initial.sort_values(by=sort_field, ascending=(sort_order == "asc"))
+ exe_ids = exe_ids_sorted['id'].tolist()
+ total_items = len(exe_ids)
+ start_idx = (page - 1) * per_page
+ end_idx = start_idx + per_page
+ if total_items < end_idx:
+ end_idx = total_items
+ exe_ids_list = exe_ids[start_idx:end_idx]
+ executions_df = get_executions_by_ids(server_store_path, pipeline_name, exe_ids_list)
+ temp = executions_df.to_json(orient="records")
+ executions_parsed = json.loads(temp)
+ return {
+ "total_items": total_items,
+ "items": executions_parsed
+ }
+ else:
+ return
+
@app.get("/display_lineage/{pipeline_name}")
async def display_lineage(request: Request, pipeline_name: str):
@@ -88,17 +148,68 @@ async def display_lineage(request: Request, pipeline_name: str):
return f"Pipeline name {pipeline_name} doesn't exist."
else:
- return 'mlmd doesnt exist'
+ return 'mlmd does not exist!!'
+
# api to display artifacts available in mlmd
-@app.get("/display_artifact_type/{pipeline_name}/{data}")
-async def display_artifact(request: Request, pipeline_name: str,data: str):
+@app.get("/display_artifacts/{pipeline_name}/{type}")
+async def display_artifact(
+ request: Request,
+ pipeline_name: str,
+ type: str, # type = artifact type
+ page: int = Query(1, description="Page number", gt=0),
+ per_page: int = Query(5, description="Items per page", le=100),
+ sort_field: str = Query("name", description="Column to sort by"),
+ sort_order: str = Query("asc", description="Sort order (asc or desc)"),
+ filter_by: str = Query(None, description="Filter by column"),
+ filter_value: str = Query(None, description="Filter value"),
+ ):
+ empty_df = pd.DataFrame()
+ art_ids_dict = {}
+ art_type = type
# checks if mlmd file exists on server
if os.path.exists(server_store_path):
- artifact_df = get_artifacts(server_store_path, pipeline_name,data)
- return artifact_df
+ art_ids_dict = dict_of_art_ids[pipeline_name]
+ if not art_ids_dict:
+ return
+ art_ids_initial = []
+ if art_type in art_ids_dict:
+ art_ids_initial = art_ids_dict[art_type]
+ else:
+ return
+ # Apply filtering if provided
+ if filter_by and filter_value:
+ art_ids_initial = art_ids_initial[art_ids_initial[filter_by].str.contains(filter_value, case=False)]
+ # Apply sorting if provided
+ art_ids_sorted = art_ids_initial.sort_values(by=sort_field, ascending=(sort_order == "asc"))
+ art_ids = art_ids_sorted['id'].tolist()
+ total_items = len(art_ids)
+ start_idx = (page - 1) * per_page
+ end_idx = start_idx + per_page
+ if total_items < end_idx:
+ end_idx = total_items
+ artifact_id_list = list(art_ids)[start_idx:end_idx]
+ artifact_df = get_artifacts(server_store_path, pipeline_name, art_type, artifact_id_list)
+ data_paginated = artifact_df
+ return {
+ "total_items": total_items,
+ "items": data_paginated
+ }
else:
- artifact_df = ""
+ return f"{server_store_path} file doesn't exist."
+
+
+
+@app.get("/display_artifact_types")
+async def display_artifact_types(request: Request):
+ # checks if mlmd file exists on server
+ if os.path.exists(server_store_path):
+ artifact_types = get_artifact_types(server_store_path)
+ return artifact_types
+ else:
+ artifact_types = ""
+ return
+
@app.get("/display_pipelines")
async def display_list_of_pipelines(request: Request):
@@ -112,3 +223,17 @@ async def display_list_of_pipelines(request: Request):
pipeline_names = []
return pipeline_names
+
+
+async def update_global_art_dict():
+ global dict_of_art_ids
+ output_dict = get_all_artifact_ids(server_store_path)
+ dict_of_art_ids = output_dict
+ return
+
+
+async def update_global_exe_dict():
+ global dict_of_exe_ids
+ output_dict = get_all_exe_ids(server_store_path)
+ dict_of_exe_ids = output_dict
+ return
diff --git a/server/requirements.txt b/server/requirements.txt
index 98cd4761..3a1a76e6 100644
--- a/server/requirements.txt
+++ b/server/requirements.txt
@@ -1,4 +1,5 @@
fastapi
+fastapi-pagination
uvicorn
pydantic
minio
diff --git a/ui/src/client.js b/ui/src/client.js
index 823f81f5..47d888f2 100644
--- a/ui/src/client.js
+++ b/ui/src/client.js
@@ -23,9 +23,25 @@ class FastAPIClient {
return client;
}
- async getArtifacts(pipelineName, type) {
+ async getArtifacts(pipelineName, type, page, sortField, sortOrder, filterBy, filterValue) {
return this.apiClient
- .get(`/display_artifact_type/${pipelineName}/${type}`)
+ .get(`/display_artifacts/${pipelineName}/${type}`, {
+ params: {
+ page: page,
+ sort_field: sortField,
+ sort_order: sortOrder,
+ filter_by: filterBy,
+ filter_value: filterValue,
+ },
+ })
+ .then(({ data }) => {
+ return data;
+ });
+ }
+
+ async getArtifactTypes() {
+ return this.apiClient
+ .get(`/display_artifact_types`)
.then(({ data }) => {
return data;
});
@@ -41,9 +57,17 @@ class FastAPIClient {
}
}
- getExecutions(pipelineName) {
+ async getExecutions(pipelineName, page, sortField, sortOrder , filterBy, filterValue) {
return this.apiClient
- .get(`/display_executions/${pipelineName}`)
+ .get(`/display_executions/${pipelineName}`, {
+ params: {
+ page: page,
+ sort_field: sortField,
+ sort_order: sortOrder,
+ filter_by: filterBy,
+ filter_value: filterValue,
+ },
+ })
.then(({ data }) => {
return data;
});
diff --git a/ui/src/components/ArtifactTable/index.css b/ui/src/components/ArtifactTable/index.css
index 900f5c34..45661af7 100644
--- a/ui/src/components/ArtifactTable/index.css
+++ b/ui/src/components/ArtifactTable/index.css
@@ -33,3 +33,7 @@
background-color: #1a365d;
border-color: #1a365d;
}
+.arrow {
+ font-weight: bold;
+ font-size: 1.2em; /* Adjust the size as needed */
+}
diff --git a/ui/src/components/ArtifactTable/index.jsx b/ui/src/components/ArtifactTable/index.jsx
index 6a8b88cd..4ca0a797 100644
--- a/ui/src/components/ArtifactTable/index.jsx
+++ b/ui/src/components/ArtifactTable/index.jsx
@@ -1,66 +1,36 @@
// ArtifactTable.jsx
-import React, { useState, useEffect } from 'react';
-import './index.css';
-const ArtifactTable = ({ artifacts }) => {
+import React, { useState, useEffect } from "react";
+import "./index.css";
+const ArtifactTable = ({ artifacts, onSort, onFilter }) => {
-const [searchQuery, setSearchQuery] = useState('');
-const [currentPage, setCurrentPage] = useState(1);
-const [itemsPerPage] = useState(5); // Number of items to display per page
-const [sortBy, setSortBy] = useState(null); // Property to sort by
-const [sortOrder, setSortOrder] = useState('asc'); // Sort order ('asc' or 'desc')
-const [expandedRow, setExpandedRow] = useState(null);
-const handleSearchChange = (event) => {
- setSearchQuery(event.target.value);
- };
-
-const handlePageChange = (page) => {
- setCurrentPage(page);
- };
-
-
-const handleSort = (property) => {
- if (sortBy === property) {
- // If currently sorted by the same property, toggle sort order
- setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
- } else {
- // If sorting by a new property, set it to ascending order by default
- setSortBy(property);
- setSortOrder('asc');
- }
- };
-
-const consistentColumns = [];
+ // Default sorting order
+ const [sortOrder, setSortOrder] = useState("Context_Type");
-const filteredData = artifacts.filter((item) =>
- (item.name && item.name.toLowerCase().includes(searchQuery.toLowerCase()))
- || (item.type && item.type.toLowerCase().includes(searchQuery.toLowerCase()))
- );
+ // Local filter value state
+ const [filterValue, setFilterValue] = useState("");
-// eslint-disable-next-line
-const sortedData = filteredData.sort((a, b) => {
- const aValue = a[sortBy];
- const bValue = b[sortBy];
+ const [expandedRow, setExpandedRow] = useState(null);
- if (aValue < bValue) return sortOrder === 'asc' ? -1 : 1;
- if (aValue > bValue) return sortOrder === 'asc' ? 1 : -1;
- return 0;
- });
+ const consistentColumns = [];
-const totalPages = Math.ceil(filteredData.length / itemsPerPage);
-const indexOfLastItem = currentPage * itemsPerPage;
-const indexOfFirstItem = indexOfLastItem - itemsPerPage;
-const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem);
+ useEffect(() => {
+ // Set initial sorting order when component mounts
+ setSortOrder("asc");
+ }, []);
-useEffect(() => {
- setCurrentPage(1); // Reset current page to 1 when search query changes
- }, [searchQuery]);
-
-useEffect(() => {
- setCurrentPage(1); // Reset current page to 1 when search query changes
- }, [artifacts]);
+ const handleSort = () => {
+ const newSortOrder = sortOrder === "asc" ? "desc" : "asc";
+ setSortOrder(newSortOrder);
+ onSort("name", newSortOrder); // Notify parent component about sorting change
+ };
+ const handleFilterChange = (event) => {
+ const value = event.target.value;
+ setFilterValue(value);
+ onFilter("name", value); // Notify parent component about filter change
+ };
-const toggleRow = (rowId) => {
+ const toggleRow = (rowId) => {
if (expandedRow === rowId) {
setExpandedRow(null);
} else {
@@ -68,79 +38,16 @@ const toggleRow = (rowId) => {
}
};
-// eslint-disable-next-line
-const renderTableData = () => {
- if (currentItems.length === 0) {
- return (
-
- No data available |
-
- );
- }
-
- return currentItems.map((item) => (
-
- {/* Render table row */}
-
- ));
- };
-
-
-const renderPagination = () => {
- if (totalPages === 1){
- return null;
- }
-
- const totalPagesToShow = 5; // Number of pages to show in the pagination
-
- const startPage = Math.max(1, currentPage - Math.floor(totalPagesToShow / 2));
- const endPage = Math.min(totalPages, startPage + totalPagesToShow - 1);
-
- const pages = Array.from({ length: endPage - startPage + 1 }, (_, index) => startPage + index);
-
- return (
-
-
- {pages.map((page) => (
-
- ))}
-
-
-
- );
- };
-
-
-
-return (
+ return (
-
+
@@ -148,61 +55,88 @@ return (
|
- id |
- handleSort('name')} className="name px-6 py-3">name
- {sortBy === 'name' && sortOrder === 'asc' && '▲'}
- {sortBy === 'name' && sortOrder === 'desc' && '▼'} |
- Type |
- Url |
- Uri |
- Git_Repo |
- Commit |
+
+ id
+ |
+
+ name {sortOrder === "asc" && ↑}
+ {sortOrder === "desc" && ↓}
+ |
+
+ execution_type_name
+ |
+
+ Url
+ |
+
+ Uri
+ |
+
+ Git_Repo
+ |
+
+ Commit
+ |
- {currentItems.map((data, index) => (
+ {artifacts.length > 0 && artifacts.map((data, index) => (
- toggleRow(index)} className="text-sm font-medium text-gray-800">
- {expandedRow === index ? '-' : '+'} |
- {data.id} |
- {data.name} |
- {data.type} |
- {data.url} |
- {data.uri} |
- {data.git_repo} |
- {data.Commit} |
-
- {expandedRow === index && (
-
-
-
-
- {Object.entries(data).map(([key, value]) => {
- if (!consistentColumns.includes(key) && value != null) {
- return (
-
-
- {key} |
- {value ? value :"Null"} |
-
-
- );
- }
- return null;
- })}
-
-
- |
-
- )}
-
+ toggleRow(index)}
+ className="text-sm font-medium text-gray-800"
+ >
+
+ {expandedRow === index ? "-" : "+"}
+ |
+ {data.id} |
+ {data.name} |
+ {data.execution_type_name} |
+ {data.url} |
+ {data.uri} |
+ {data.git_repo} |
+ {data.Commit} |
+
+ {expandedRow === index && (
+
+
+
+
+ {Object.entries(data).map(([key, value]) => {
+ if (
+ !consistentColumns.includes(key) &&
+ value != null
+ ) {
+ return (
+
+
+ {key} |
+
+ {value ? value : "Null"}
+ |
+
+
+ );
+ }
+ return null;
+ })}
+
+
+ |
+
+ )}
+
))}
-
-
{renderPagination()}
-
+
+
);
};
diff --git a/ui/src/components/ExecutionTable/index.css b/ui/src/components/ExecutionTable/index.css
index 7d98f9e0..d79cdb3e 100644
--- a/ui/src/components/ExecutionTable/index.css
+++ b/ui/src/components/ExecutionTable/index.css
@@ -9,3 +9,7 @@
background-color: #1a365d;
border-color: #1a365d;
}
+.arrow {
+ font-weight: bold;
+ font-size: 1.2em; /* Adjust the size as needed */
+}
diff --git a/ui/src/components/ExecutionTable/index.jsx b/ui/src/components/ExecutionTable/index.jsx
index b1608a8b..0e2afbdb 100644
--- a/ui/src/components/ExecutionTable/index.jsx
+++ b/ui/src/components/ExecutionTable/index.jsx
@@ -1,67 +1,37 @@
//ExecutionTable.jsx
-import React, { useState, useEffect } from 'react';
-import './index.css';
+import React, { useState, useEffect } from "react";
+import "./index.css";
-const ExecutionTable = ({ executions }) => {
-
-const [searchQuery, setSearchQuery] = useState('');
-const [currentPage, setCurrentPage] = useState(1);
-const [itemsPerPage] = useState(5); // Number of items to display per page
-const [sortBy, setSortBy] = useState(null); // Property to sort by
-const [sortOrder, setSortOrder] = useState('asc'); // Sort order ('asc' or 'desc')
-const [expandedRow, setExpandedRow] = useState(null);
-const handleSearchChange = (event) => {
- setSearchQuery(event.target.value);
- };
-
-const handlePageChange = (page) => {
- setCurrentPage(page);
- };
+const ExecutionTable = ({ executions, onSort, onFilter }) => {
+ // Default sorting order
+ const [sortOrder, setSortOrder] = useState("Context_Type");
-const handleSort = (property) => {
- if (sortBy === property) {
- // If currently sorted by the same property, toggle sort order
- setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
- } else {
- // If sorting by a new property, set it to ascending order by default
- setSortBy(property);
- setSortOrder('asc');
- }
- };
-const consistentColumns = [];
+ // Local filter value state
+ const [filterValue, setFilterValue] = useState("");
-const filteredData = executions.filter((item) =>
- (item.Context_Type && item.Context_Type.toLowerCase().includes(searchQuery.toLowerCase()))
- || (item.Execution && item.Execution.toLowerCase().includes(searchQuery.toLowerCase()))
- );
+ const [expandedRow, setExpandedRow] = useState(null);
-// eslint-disable-next-line
-const sortedData = filteredData.sort((a, b) => {
- const aValue = a[sortBy];
- const bValue = b[sortBy];
+ const consistentColumns = [];
- if (aValue < bValue) return sortOrder === 'asc' ? -1 : 1;
- if (aValue > bValue) return sortOrder === 'asc' ? 1 : -1;
- return 0;
- });
-
-const totalPages = Math.ceil(filteredData.length / itemsPerPage);
-const indexOfLastItem = currentPage * itemsPerPage;
-const indexOfFirstItem = indexOfLastItem - itemsPerPage;
-const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem);
-
-useEffect(() => {
- setCurrentPage(1); // Reset current page to 1 when search query changes
- }, [searchQuery]);
-
-useEffect(() => {
- setCurrentPage(1); // Reset current page to 1 when search query changes
- }, [executions]);
+ useEffect(() => {
+ // Set initial sorting order when component mounts
+ setSortOrder("asc");
+ }, []);
+ const handleSort = () => {
+ const newSortOrder = sortOrder === "asc" ? "desc" : "asc";
+ setSortOrder(newSortOrder);
+ onSort("Context_Type", newSortOrder); // Notify parent component about sorting change
+ };
+ const handleFilterChange = (event) => {
+ const value = event.target.value;
+ setFilterValue(value);
+ onFilter("Context_Type", value); // Notify parent component about filter change
+ };
-const toggleRow = (rowId) => {
+ const toggleRow = (rowId) => {
if (expandedRow === rowId) {
setExpandedRow(null);
} else {
@@ -69,79 +39,26 @@ const toggleRow = (rowId) => {
}
};
-// eslint-disable-next-line
-const renderTableData = () => {
- if (currentItems.length === 0) {
- return (
-
- No data available |
-
- );
- }
-
- return currentItems.map((item) => (
-
- {/* Render table row */}
-
- ));
- };
-
-
-const renderPagination = () => {
- if (totalPages === 1){
- return null;
- }
-
- const totalPagesToShow = 5; // Number of pages to show in the pagination
-
- const startPage = Math.max(1, currentPage - Math.floor(totalPagesToShow / 2));
- const endPage = Math.min(totalPages, startPage + totalPagesToShow - 1);
-
- const pages = Array.from({ length: endPage - startPage + 1 }, (_, index) => startPage + index);
-
- return (
-
-
- {pages.map((page) => (
-
- ))}
-
-
-
- );
- };
-
-
-
-return (
+ return (
-
-
+
+
@@ -149,59 +66,81 @@ return (
|
- handleSort('Context_Type')} className="px-6 py-3 Context_Type">Context_Type
- {sortBy === 'Context_Type' && sortOrder === 'asc' && '▲'}
- {sortBy === 'Context_Type' && sortOrder === 'desc' && '▼'} |
- Execution |
- Git_Repo |
- Git_Start_Commit |
- Pipeline_Type |
+
+ Context_Type {sortOrder === "asc" && ↑}
+ {sortOrder === "desc" && ↓}
+ |
+
+ Execution
+ |
+
+ Git_Repo
+ |
+
+ Git_Start_Commit
+ |
+
+ Pipeline_Type
+ |
- {currentItems.map((data, index) => (
+ {executions.map((data, index) => (
- toggleRow(index)} className="text-sm font-medium text-gray-800">
- {expandedRow === index ? '-' : '+'} |
- {data.Context_Type} |
- {data.Execution} |
- {data.Git_Repo} |
- {data.Git_Start_Commit} |
- {data.Pipeline_Type} |
-
- {expandedRow === index && (
-
-
-
-
- {Object.entries(data).map(([key, value]) => {
- if (!consistentColumns.includes(key) && value != null) {
- return (
-
-
- {key} |
- {value ? value :"Null"} |
-
-
- );
- }
- return null;
- })}
-
-
- |
-
- )}
-
+ toggleRow(index)}
+ className="text-sm font-medium text-gray-800"
+ >
+
+ {expandedRow === index ? "-" : "+"}
+ |
+ {data.Context_Type} |
+ {data.Execution} |
+ {data.Git_Repo} |
+ {data.Git_Start_Commit} |
+ {data.Pipeline_Type} |
+
+ {expandedRow === index && (
+
+
+
+
+ {Object.entries(data).map(([key, value]) => {
+ if (
+ !consistentColumns.includes(key) &&
+ value != null
+ ) {
+ return (
+
+
+ {key} |
+
+ {value ? value : "Null"}
+ |
+
+
+ );
+ }
+ return null;
+ })}
+
+
+ |
+
+ )}
+
))}
-
-
{renderPagination()}
-
+
+
);
};
-
export default ExecutionTable;
diff --git a/ui/src/pages/artifacts/index.css b/ui/src/pages/artifacts/index.css
index ada97ab9..97a4c34d 100644
--- a/ui/src/pages/artifacts/index.css
+++ b/ui/src/pages/artifacts/index.css
@@ -75,3 +75,16 @@ body {
height: 5px;
background: rgb(88, 147, 241);
}
+
+
+.active {
+ background-color: #2c5282;
+ color: #fff;
+ font-weight: bold;
+ border-color: #2c5282;
+}
+
+.active:hover {
+ background-color: #1a365d;
+ border-color: #1a365d;
+}
diff --git a/ui/src/pages/artifacts/index.jsx b/ui/src/pages/artifacts/index.jsx
index 0b2cfc67..0298f2e7 100644
--- a/ui/src/pages/artifacts/index.jsx
+++ b/ui/src/pages/artifacts/index.jsx
@@ -16,6 +16,17 @@ const Artifacts = () => {
const [artifacts, setArtifacts] = useState([]);
const [artifactTypes, setArtifactTypes] = useState([]);
const [selectedArtifactType, setSelectedArtifactType] = useState(null);
+ const [totalItems, setTotalItems] = useState(0);
+ const [activePage, setActivePage] = useState(1);
+ const [clickedButton, setClickedButton] = useState("page");
+ // Default sort field
+ const [sortField, setSortField] = useState("name");
+ // Default sort order
+ const [sortOrder, setSortOrder] = useState("asc");
+ // Default filter field
+ const [filterBy, setFilterBy] = useState(null);
+ // Default filter value
+ const [filterValue, setFilterValue] = useState(null);
const fetchPipelines = () => {
client.getPipelines("").then((data) => {
@@ -24,55 +35,80 @@ const Artifacts = () => {
});
};
-
- useEffect(() => {
- fetchPipelines();
- }, []);
+ useEffect(() => {
+ fetchPipelines();
+ }, []);
const handlePipelineClick = (pipeline) => {
- setSelectedPipeline(pipeline)
+ setSelectedPipeline(pipeline);
+ setActivePage(1);
};
const handleArtifactTypeClick = (artifactType) => {
setSelectedArtifactType(artifactType);
+ setActivePage(1);
};
-
- const fetchArtifactTypes = (pipelineName) => {
- client.getArtifacts(pipelineName, "artifact_type").then((types) => {
+ const fetchArtifactTypes = () => {
+ client.getArtifactTypes().then((types) => {
setArtifactTypes(types);
- handleArtifactTypeClick(types[0])
+ handleArtifactTypeClick(types[0]);
});
};
-
- useEffect(() => {
- if(selectedPipeline) {
+ useEffect(() => {
+ if (selectedPipeline) {
fetchArtifactTypes(selectedPipeline);
}
// eslint-disable-next-line
}, [selectedPipeline]);
-
-
- const fetchArtifacts = (pipelineName, type) => {
- client.getArtifacts(pipelineName, type).then((data) => {
- setArtifacts(data);
+ const fetchArtifacts = (pipelineName, type, page, sortField, sortOrder, filterBy, filterValue) => {
+ client.getArtifacts(pipelineName, type, page, sortField, sortOrder, filterBy, filterValue).then((data) => {
+ setArtifacts(data.items);
+ setTotalItems(data.total_items);
});
};
-
+ useEffect(() => {
+ if (selectedPipeline && selectedArtifactType) {
+ fetchArtifacts(selectedPipeline, selectedArtifactType, activePage, sortField, sortOrder, filterBy, filterValue);
+ }
+ }, [selectedPipeline, selectedArtifactType, activePage, sortField, sortOrder, filterBy, filterValue]);
+
+ const handlePageClick = (page) => {
+ setActivePage(page);
+ setClickedButton("page");
+ };
+ const handlePrevClick = () => {
+ if (activePage > 1) {
+ setActivePage(activePage - 1);
+ setClickedButton("prev");
+ handlePageClick(activePage - 1);
+ }
+ };
- useEffect(() => {
- if(selectedPipeline && selectedArtifactType) {
- fetchArtifacts(selectedPipeline, selectedArtifactType);
+ const handleNextClick = () => {
+ if (activePage < Math.ceil(totalItems / 5)) {
+ setActivePage(activePage + 1);
+ setClickedButton("next");
+ handlePageClick(activePage + 1);
}
+ };
+
+ const handleSort = (newSortField, newSortOrder) => {
+ setSortField(newSortField);
+ setSortOrder(newSortOrder);
+ };
- }, [selectedPipeline, selectedArtifactType]);
+ const handleFilter = (field, value) => {
+ setFilterBy(field);
+ setFilterValue(value);
+ };
-return (
+ return (
<>
-
+
-
+
{selectedPipeline !== null && (
-
+
)}
-
{selectedPipeline !== null && selectedArtifactType !== null && (
-
+
)}
+
+ {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;
+ }
+ )}
+
+ >
+ )}
+
diff --git a/ui/src/pages/executions/index.css b/ui/src/pages/executions/index.css
index e8895220..73b44a4c 100644
--- a/ui/src/pages/executions/index.css
+++ b/ui/src/pages/executions/index.css
@@ -86,3 +86,15 @@ button {
.active-content {
display: block;
}
+
+.active {
+ background-color: #2c5282;
+ color: #fff;
+ font-weight: bold;
+ border-color: #2c5282;
+}
+
+.active:hover {
+ background-color: #1a365d;
+ border-color: #1a365d;
+}
diff --git a/ui/src/pages/executions/index.jsx b/ui/src/pages/executions/index.jsx
index 74d79541..ba7eb991 100644
--- a/ui/src/pages/executions/index.jsx
+++ b/ui/src/pages/executions/index.jsx
@@ -10,10 +10,20 @@ import Sidebar from "../../components/Sidebar";
const client = new FastAPIClient(config);
const Executions = () => {
-
const [pipelines, setPipelines] = useState([]);
const [selectedPipeline, setSelectedPipeline] = useState(null);
const [executions, setExecutions] = useState([]);
+ const [totalItems, setTotalItems] = useState(0);
+ const [activePage, setActivePage] = useState(1);
+ const [clickedButton, setClickedButton] = useState("page");
+ // Default sort field
+ const [sortField, setSortField] = useState("Context_Type");
+ // Default sort order
+ const [sortOrder, setSortOrder] = useState("asc");
+ // Default filter field
+ const [filterBy, setFilterBy] = useState(null);
+ // Default filter value
+ const [filterValue, setFilterValue] = useState(null);
useEffect(() => {
fetchPipelines();
@@ -27,20 +37,52 @@ const Executions = () => {
};
useEffect(() => {
- if(selectedPipeline) {
- fetchExecutions(selectedPipeline);
+ if (selectedPipeline) {
+ fetchExecutions(selectedPipeline, activePage, sortField, sortOrder, filterBy, filterValue);
}
+ }, [selectedPipeline, activePage, sortField, sortOrder, filterBy, filterValue]);
- }, [selectedPipeline]);
-
- const fetchExecutions = (pipelineName) => {
- client.getExecutions(pipelineName).then((data) => {
- setExecutions(data);
+ const fetchExecutions = (pipelineName, page, sortField, sortOrder, filterBy, filterValue) => {
+ client.getExecutions(pipelineName, page, sortField, sortOrder, filterBy, filterValue).then((data) => {
+ setExecutions(data.items);
+ setTotalItems(data.total_items);
});
};
const handlePipelineClick = (pipeline) => {
- setSelectedPipeline(pipeline)
+ setSelectedPipeline(pipeline);
+ setActivePage(1);
+ };
+
+ const handlePageClick = (page) => {
+ setActivePage(page);
+ setClickedButton("page");
+ };
+
+ const handlePrevClick = () => {
+ if (activePage > 1) {
+ setActivePage(activePage - 1);
+ setClickedButton("prev");
+ handlePageClick(activePage - 1);
+ }
+ };
+
+ const handleNextClick = () => {
+ if (activePage < Math.ceil(totalItems / 5)) {
+ setActivePage(activePage + 1);
+ setClickedButton("next");
+ handlePageClick(activePage + 1);
+ }
+ };
+
+ const handleSort = (newSortField, newSortOrder) => {
+ setSortField(newSortField);
+ setSortOrder(newSortOrder);
+ };
+
+ const handleFilter = (field, value) => {
+ setFilterBy(field);
+ setFilterValue(value);
};
return (
@@ -51,11 +93,92 @@ const Executions = () => {
>
-
+
{selectedPipeline !== 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;
+ }
+ )}
+
+ >
)}