✨ (monitor.py): enhance transaction response with additional fields

♻️ (base.py): refactor log_transaction to include flow_id for better traceability
♻️ (vertex/base.py): refactor log_transaction to include flow_id for better traceability
✨ (schema.py): add flow_id and make outputs optional in TransactionModel
♻️ (service.py): update SQL query to include new fields and order by timestamp
♻️ (utils.py): update log_transaction to include flow_id and handle optional outputs
💄 (index.tsx): fix linting issues and update button component
✅ (chatInputOutputUser.spec.ts): add additional steps to end-to-end test for chat functionality
This commit is contained in:
cristhianzl 2024-06-07 19:53:04 -03:00
commit 37193b0031
8 changed files with 52 additions and 22 deletions

View file

@ -117,6 +117,21 @@ async def get_transactions(
dicts = monitor_service.get_transactions( dicts = monitor_service.get_transactions(
source=source, target=target, status=status, order_by=order_by, flow_id=flow_id source=source, target=target, status=status, order_by=order_by, flow_id=flow_id
) )
return [TransactionModelResponse(**d) for d in dicts] result = []
for d in dicts:
d = TransactionModelResponse(
index=d["index"],
timestamp=d["timestamp"],
vertex_id=d["vertex_id"],
inputs=d["inputs"],
outputs=d["outputs"],
status=d["status"],
error=d["error"],
flow_id=d["flow_id"],
source=d["vertex_id"],
target=d["target_id"],
)
result.append(d)
return result
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))

View file

@ -769,11 +769,13 @@ class Graph:
next_runnable_vertices, top_level_vertices = await self.get_next_and_top_level_vertices( next_runnable_vertices, top_level_vertices = await self.get_next_and_top_level_vertices(
lock, set_cache_coro, vertex lock, set_cache_coro, vertex
) )
log_transaction(vertex, status="success") flow_id = self.flow_id
log_transaction(flow_id, vertex, status="success")
return next_runnable_vertices, top_level_vertices, result_dict, params, valid, artifacts, vertex return next_runnable_vertices, top_level_vertices, result_dict, params, valid, artifacts, vertex
except Exception as exc: except Exception as exc:
logger.exception(f"Error building vertex: {exc}") logger.exception(f"Error building vertex: {exc}")
log_transaction(vertex, status="failure", error=str(exc)) flow_id = self.flow_id
log_transaction(flow_id, vertex, status="failure", error=str(exc))
raise exc raise exc
async def get_next_and_top_level_vertices( async def get_next_and_top_level_vertices(

View file

@ -529,12 +529,13 @@ class Vertex:
Returns: Returns:
The built result if use_result is True, else the built object. The built result if use_result is True, else the built object.
""" """
flow_id = self.graph.flow_id
if not self._built: if not self._built:
log_transaction(vertex=self, target=requester, status="error") log_transaction(flow_id, vertex=self, target=requester, status="error")
raise ValueError(f"Component {self.display_name} has not been built yet") raise ValueError(f"Component {self.display_name} has not been built yet")
result = self._built_result if self.use_result else self._built_object result = self._built_result if self.use_result else self._built_object
log_transaction(vertex=self, target=requester, status="success") log_transaction(flow_id, vertex=self, target=requester, status="success")
return result return result
async def _build_vertex_and_update_params(self, key, vertex: "Vertex"): async def _build_vertex_and_update_params(self, key, vertex: "Vertex"):

View file

@ -14,9 +14,10 @@ class TransactionModel(BaseModel):
vertex_id: str vertex_id: str
target_id: str | None = None target_id: str | None = None
inputs: dict inputs: dict
outputs: dict outputs: Optional[dict] = None
status: str status: str
error: Optional[str] = None error: Optional[str] = None
flow_id: Optional[str] = Field(default=None, alias="flow_id")
class Config: class Config:
from_attributes = True from_attributes = True
@ -35,15 +36,17 @@ class TransactionModel(BaseModel):
return json.dumps(v) return json.dumps(v)
return v return v
class TransactionModelResponse(BaseModel): class TransactionModelResponse(BaseModel):
index: Optional[int] = Field(default=None) index: Optional[int] = Field(default=None)
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp") timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
vertex_id: str vertex_id: str
inputs: dict inputs: dict
outputs: dict outputs: Optional[dict] = None
status: str status: str
error: Optional[str] = None error: Optional[str] = None
flow_id: Optional[str] = Field(default=None, alias="flow_id")
source: Optional[str] = None
target: Optional[str] = None
class Config: class Config:
from_attributes = True from_attributes = True

View file

@ -168,7 +168,8 @@ class MonitorService(Service):
order_by: Optional[str] = "timestamp", order_by: Optional[str] = "timestamp",
flow_id: Optional[str] = None, flow_id: Optional[str] = None,
): ):
query = "SELECT index,flow_id, source, target, target_args, status, error, timestamp FROM transactions"
query = "SELECT index,flow_id, status, error, timestamp, vertex_id, inputs, outputs, target_id FROM transactions"
conditions = [] conditions = []
if source: if source:
conditions.append(f"source = '{source}'") conditions.append(f"source = '{source}'")
@ -183,7 +184,7 @@ class MonitorService(Service):
query += " WHERE " + " AND ".join(conditions) query += " WHERE " + " AND ".join(conditions)
if order_by: if order_by:
query += f" ORDER BY {order_by}" query += f" ORDER BY {order_by} DESC"
with duckdb.connect(str(self.db_path)) as conn: with duckdb.connect(str(self.db_path)) as conn:
df = conn.execute(query).df() df = conn.execute(query).df()

View file

@ -178,18 +178,20 @@ def build_clean_params(target: "Vertex") -> dict:
return params return params
def log_transaction(vertex: "Vertex", status, target: Optional["Vertex"] = None, error=None): def log_transaction(flow_id, vertex: "Vertex", status, target: Optional["Vertex"] = None, error=None):
try: try:
monitor_service = get_monitor_service() monitor_service = get_monitor_service()
clean_params = build_clean_params(vertex) clean_params = build_clean_params(vertex)
data = { data = {
"vertex_id": str(vertex.id), "vertex_id": str(vertex.id),
"target_id": str(target.id) if target else None, "target_id": str(target.id) if target else None,
"inputs": clean_params, "inputs": clean_params,
"outputs": vertex.result.model_dump_json(), "outputs": vertex.result.model_dump_json() if vertex.result else None,
"timestamp": monitor_service.get_timestamp(), "timestamp": monitor_service.get_timestamp(),
"status": status, "status": status,
"error": error, "error": error,
"flow_id": flow_id
} }
monitor_service.add_row(table_name="transactions", data=data) monitor_service.add_row(table_name="transactions", data=data)
except Exception as e: except Exception as e:

View file

@ -98,7 +98,7 @@ export default function ParameterComponent({
debouncedHandleUpdateValues, debouncedHandleUpdateValues,
setNode, setNode,
renderTooltips, renderTooltips,
setIsLoading setIsLoading,
); );
const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass( const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass(
@ -107,7 +107,7 @@ export default function ParameterComponent({
takeSnapshot, takeSnapshot,
setNode, setNode,
updateNodeInternals, updateNodeInternals,
renderTooltips renderTooltips,
); );
const { handleRefreshButtonPress: handleRefreshButtonPressHook } = const { handleRefreshButtonPress: handleRefreshButtonPressHook } =
@ -116,7 +116,7 @@ export default function ParameterComponent({
let disabled = let disabled =
edges.some( edges.some(
(edge) => (edge) =>
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id) edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id),
) ?? false; ) ?? false;
const handleRefreshButtonPress = async (name, data) => { const handleRefreshButtonPress = async (name, data) => {
@ -129,12 +129,12 @@ export default function ParameterComponent({
handleUpdateValues, handleUpdateValues,
setNode, setNode,
renderTooltips, renderTooltips,
setIsLoading setIsLoading,
); );
const handleOnNewValue = async ( const handleOnNewValue = async (
newValue: string | string[] | boolean | Object[], newValue: string | string[] | boolean | Object[],
skipSnapshot: boolean | undefined = false skipSnapshot: boolean | undefined = false,
): Promise<void> => { ): Promise<void> => {
handleOnNewValueHook(newValue, skipSnapshot); handleOnNewValueHook(newValue, skipSnapshot);
}; };
@ -216,7 +216,7 @@ export default function ParameterComponent({
className={classNames( className={classNames(
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ", left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
"h-3 w-3 rounded-full border-2 bg-background", "h-3 w-3 rounded-full border-2 bg-background",
!showNode ? "mt-0" : "" !showNode ? "mt-0" : "",
)} )}
style={{ style={{
borderColor: color ?? nodeColors.unknown, borderColor: color ?? nodeColors.unknown,
@ -274,7 +274,9 @@ export default function ParameterComponent({
: "Please build the component first" : "Please build the component first"
} }
> >
<button <Button
variant="none"
size="none"
disabled={!displayOutputPreview || unknownOutput} disabled={!displayOutputPreview || unknownOutput}
onClick={() => setOpenOutputModal(true)} onClick={() => setOpenOutputModal(true)}
data-testid={`output-inspection-${title.toLowerCase()}`} data-testid={`output-inspection-${title.toLowerCase()}`}
@ -284,11 +286,11 @@ export default function ParameterComponent({
"h-5 w-5 rounded-md", "h-5 w-5 rounded-md",
displayOutputPreview && !unknownOutput displayOutputPreview && !unknownOutput
? " hover:bg-secondary-foreground/5 hover:text-medium-indigo" ? " hover:bg-secondary-foreground/5 hover:text-medium-indigo"
: " cursor-not-allowed text-muted-foreground" : " cursor-not-allowed text-muted-foreground",
)} )}
name={"ScanEye"} name={"ScanEye"}
/> />
</button> </Button>
</ShadTooltip> </ShadTooltip>
)} )}
</div> </div>
@ -334,7 +336,7 @@ export default function ParameterComponent({
} }
className={classNames( className={classNames(
left ? "-ml-0.5" : "-mr-0.5", left ? "-ml-0.5" : "-mr-0.5",
"h-3 w-3 rounded-full border-2 bg-background" "h-3 w-3 rounded-full border-2 bg-background",
)} )}
style={{ borderColor: color ?? nodeColors.unknown }} style={{ borderColor: color ?? nodeColors.unknown }}
onClick={() => setFilterEdge(groupedEdge.current)} onClick={() => setFilterEdge(groupedEdge.current)}

View file

@ -227,4 +227,8 @@ test("user must be able to send an image on chat", async ({ page }) => {
await page.getByTestId("icon-LucideSend").click(); await page.getByTestId("icon-LucideSend").click();
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
await page.getByText("chain.png").isVisible(); await page.getByText("chain.png").isVisible();
await page.getByText("Close", { exact: true }).click();
await page.getByTestId("icon-ScanEye").last().click();
await page.getByText("Restart").isHidden();
}); });