Skip to content

Commit

Permalink
feat: Support singe-instance templates
Browse files Browse the repository at this point in the history
  • Loading branch information
huyenngn committed Apr 8, 2024
1 parent 1596765 commit 2e8a248
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 80 deletions.
13 changes: 9 additions & 4 deletions capella_model_explorer/backend/explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ def read_template(template_name: str):
if not template_name in self.templates:
return {"error": f"Template {template_name} not found"}
base = self.templates[urlparse.quote(template_name)]
filters = base.get("filters") or None
base["single"] = base.get("single", False)
filters = base.get("filters")
variable = base["variable"]
below = variable.get("below") or None
attr = variable.get("attr") or None
Expand Down Expand Up @@ -180,15 +181,19 @@ def index_templates(
return templates_grouped, templates


def find_objects(model, obj_type, below=None, attr=None, filters=None):
def find_objects(model, obj_type=None, below=None, attr=None, filters=None):
if attr:
getter = operator.attrgetter(attr)
objects = getter(model)
elif below:
if objects and not isinstance(objects, list):
objects = [objects]
elif below and obj_type:
getter = operator.attrgetter(below)
objects = model.search(obj_type, below=getter(model))
else:
elif obj_type:
objects = model.search(obj_type)
else:
raise ValueError("No search criteria provided")

if filters:
objects = [
Expand Down
147 changes: 89 additions & 58 deletions frontend/src/components/TemplateDetails.jsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,117 @@
// Copyright DB InfraGO AG and contributors
// SPDX-License-Identifier: Apache-2.0

import React, {useEffect, useState} from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { Card } from './Card';
import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";

export const TemplateDetails = ({endpoint}) => {
export const TemplateDetails = ({ endpoint, onSingleInstance }) => {
let { templateName, objectID } = useParams();
const [error, setError] = useState(null);
const [details, setDetails] = useState([]);
const navigate = useNavigate();
const [filterText, setFilterText] = useState('');
const [filterText, setFilterText] = useState("");

useEffect(() => {
const fetchDetails = async () => {
try {
const response = await fetch(endpoint + templateName, {
method: 'GET',
method: "GET",
headers: {
'Content-Type': 'application/json'
"Content-Type": "application/json",
},
});
const data = await response.json();
setDetails(data);
}
catch (error) {
if (data.single) {
onSingleInstance(data.objects[0].idx);
}
} catch (error) {
setError(error.message);
} finally {
}
finally {}
};
fetchDetails();
}, [endpoint, templateName, objectID]);
}, [endpoint, templateName, objectID, onSingleInstance]);
if (error) {
return (
<div className='bg-red-500 text-white p-4 rounded text-2xl'>
{error}
</div>
)
<div className="bg-red-500 text-white p-4 rounded text-2xl">
{error}
</div>
);
}
return (
<div className='flex flex-col h-full'>
<h5 className='mb-2 text-2xl font-bold text-gray-900 dark:text-white'>
<div className="flex flex-col h-full">
<h5 className="mb-2 text-2xl font-bold text-gray-900 dark:text-white">
{details.name}
</h5>
<p className='mb-3 font-normal text-gray-700 dark:text-white'>{details.description}</p>
{details.error ? (
<div>
<p>We failed to find matching template instances due to the following error: </p>
<div className='bg-red-500 py-2 px-2 text-white rounded'>
{details.error}
</div>
</div>
) : (
<>
<input
type="text"
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
placeholder="Filter objects"
className="mb-3 p-2 border rounded"
/>
<div className='flex flex-wrap justify-center items-center overflow-auto'>
{details.objects && details.objects.length === 0 ? (
<p>No objects found</p>
) : (
details.objects && details.objects.filter(object => object.name.toLowerCase().includes(filterText.toLowerCase())).map(object => (
<div key={object.idx}
onClick={() => {navigate(`/${templateName}/${object.idx}`);}}
className={(objectID && object.idx === objectID ? 'bg-blue-800 dark:bg-blue-800 text-white hover:dark:text-white hover:text-blue-800' : 'text-gray-900') + ' max-w-sm rounded-lg border border-gray-200 shadow-md m-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 dark:border-gray-700'}
>
<div className='p-2'>
<h5 className={'text-md font-bold dark:text-white ' + (objectID && object.idx === objectID ? '' : '')}>
{object.name}
</h5>
</h5>
<p className="mb-3 font-normal text-gray-700 dark:text-white">
{details.description}
</p>
{details.error ? (
<div>
<p>
We failed to find matching template instances due to
the following error:{" "}
</p>
<div className="bg-red-500 py-2 px-2 text-white rounded">
{details.error}
</div>
</div>
</div>
))
)}
</div>
</>
)}

</div>
);}
) : (
<>
<input
type="text"
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
placeholder="Filter objects"
className="mb-3 p-2 border rounded"
/>
<div className="flex flex-wrap justify-center items-center overflow-auto">
{details.objects && details.objects.length === 0 ? (
<p>No objects found</p>
) : (
details.objects &&
details.objects
.filter((object) =>
object.name
.toLowerCase()
.includes(filterText.toLowerCase())
)
.map((object) => (
<div
key={object.idx}
onClick={() => {
navigate(
`/${templateName}/${object.idx}`
);
}}
className={
(objectID &&
object.idx === objectID
? "bg-blue-800 dark:bg-blue-800 text-white hover:dark:text-white hover:text-blue-800"
: "text-gray-900") +
" max-w-sm rounded-lg border border-gray-200 shadow-md m-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 dark:border-gray-700"
}
>
<div className="p-2">
<h5
className={
"text-md font-bold dark:text-white " +
(objectID &&
object.idx === objectID
? ""
: "")
}
>
{object.name}
</h5>
</div>
</div>
))
)}
</div>
</>
)}
</div>
);
};
55 changes: 37 additions & 18 deletions frontend/src/views/TemplateView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,58 @@ In this component we show list of template instances, and when we click on a tem
*/

import React, {useEffect, useState} from 'react';
import { TemplateDetails } from '../components/TemplateDetails';
import { useLocation, useParams, Navigate } from 'react-router-dom';
import { InstanceView } from '../components/InstanceView';
import { Header } from '../components/Header';
import React, { useEffect, useState } from "react";
import { useLocation, useParams } from "react-router-dom";
import { Header } from "../components/Header";
import { InstanceView } from "../components/InstanceView";
import { TemplateDetails } from "../components/TemplateDetails";

export const TemplateView = ({endpoint}) => {
export const TemplateView = ({ endpoint }) => {
let { templateName, objectID } = useParams();
const [singleObjectID, setObjectID] = useState(null);
const location = useLocation();

useEffect(() => {
}, [endpoint, templateName, objectID, location]);
useEffect(() => {}, [endpoint, templateName, objectID, location]);

return (
<div className="flex flex-col h-screen"> {/* Use h-screen to ensure the container fits the viewport height */}
<div className="flex flex-col h-screen">
{" "}
{/* Use h-screen to ensure the container fits the viewport height */}
{/* Header */}
<Header />

{/* Body: Sidebar + Main Content */}
<div className="flex flex-1 overflow-hidden"> {/* This ensures the remaining height is distributed here */}
<div className="flex flex-1 overflow-hidden">
{" "}
{/* This ensures the remaining height is distributed here */}
{/* Sidebar - Adjust visibility/responsiveness as needed */}
<aside className="hidden lg:block lg:w-80 p-4 overflow-y-auto"> {/* Use overflow-y-auto to enable vertical scrolling */}
<TemplateDetails endpoint={endpoint} />
<aside className="hidden lg:block lg:w-80 p-4 overflow-y-auto">
{" "}
{/* Use overflow-y-auto to enable vertical scrolling */}
<TemplateDetails
endpoint={endpoint}
onSingleInstance={setObjectID}
/>
</aside>

{/* Main Content */}
<main className="flex-1 overflow-hidden p-4">
<div className="w-full p-4 max-w-none lg:max-w-4xl min-w-0 lg:min-w-[850px] overflow-y-hidden h-full flex items-center justify-center"> {/* Ensure main content is scrollable and fills the height */}
{ !!!objectID && <p className='text-xl text-gray-700'>Select an Instance</p>}
{ objectID && <InstanceView endpoint={endpoint} objectID={objectID} templateName={templateName} /> }
<div className="w-full p-4 max-w-none lg:max-w-4xl min-w-0 lg:min-w-[850px] overflow-y-hidden h-full flex items-center justify-center">
{" "}
{/* Ensure main content is scrollable and fills the height */}
{!!!objectID && !!!singleObjectID && (
<p className="text-xl text-gray-700">
Select an Instance
</p>
)}
{(objectID || singleObjectID) && (
<InstanceView
endpoint={endpoint}
objectID={objectID || singleObjectID}
templateName={templateName}
/>
)}
</div>
</main>
</div>
</div>
);
}
};
5 changes: 5 additions & 0 deletions templates/system-definition.html.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{#
Copyright DB InfraGO AG and contributors
SPDX-License-Identifier: Apache-2.0
#}
<h1>{{ object.name }}</h1>
14 changes: 14 additions & 0 deletions templates/system-definition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: Apache-2.0

template: system-definition.html.j2
name: System Definition
description: hi
category: sa
single: true
variable:
name: object
type: SystemComponent
below: sa
filters:
name: "System"

0 comments on commit 2e8a248

Please sign in to comment.