Skip to content

Commit

Permalink
Merge pull request #779 from near/develop
Browse files Browse the repository at this point in the history
non-regular promotion develop -> main
  • Loading branch information
gabehamilton authored Apr 22, 2024
2 parents 0983e88 + faf4933 commit f7f8e2e
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 44 deletions.
51 changes: 31 additions & 20 deletions src/Entities/Template/EntityDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ if (!loadItem) {
}
const { src, tab, schemaFile, namespace, returnTo } = props; // url params
const [accountId, entityType, entityName] = src.split("/") ?? [null, null, null];
const { additionalTabs } = props; // inline params

const summaryComponent = props.summaryComponent ?? "${REPL_ACCOUNT}/widget/Entities.Template.EntitySummary";
const schemaLocation = schemaFile ? schemaFile : `${REPL_ACCOUNT}/widget/Entities.Template.GenericSchema`;
Expand Down Expand Up @@ -54,7 +55,7 @@ const onLoad = (itemInArray) => {
return;
}
const fetchedItem = itemInArray[0];
const fullEntity = convertObjectKeysSnakeToPascal(fetchedItem);
const fullEntity = convertObjectKeysSnakeToPascal({ ...fetchedItem, ...fetchedItem?.attributes });
setEntity(fullEntity);
};
loadItem(query, "SingleEntity", collection, onLoad);
Expand Down Expand Up @@ -146,6 +147,32 @@ const listLink = href({
widgetSrc: returnTo,
});

const tabs = () => {
const defaultTabs = [
{
name: "Properties",
value: "properties",
content: entityProperties(entity),
icon: "ph ph-code",
},
{
name: editLabel,
value: "edit",
content: (
<Widget
src={"${REPL_ACCOUNT}/widget/Entities.Template.EntityCreate"}
props={{ entityType, schema, data: entity, cancelLabel: "Clear changes" }}
/>
),
icon: editIcon,
},
];
if (additionalTabs) {
return defaultTabs.concat(additionalTabs(entity));
}
return defaultTabs;
};

return (
<Wrapper>
{returnTo && (
Expand All @@ -162,6 +189,8 @@ return (
size: "small",
showTags: true,
entity,
namespace,
entityType,
showActions: true,
returnTo,
}}
Expand All @@ -173,25 +202,7 @@ return (
variant: "line",
size: "large",
defaultValue: "properties",
items: [
{
name: "Properties",
value: "properties",
content: entityProperties(entity),
icon: "ph ph-code",
},
{
name: editLabel,
value: "edit",
content: (
<Widget
src={"${REPL_ACCOUNT}/widget/Entities.Template.EntityCreate"}
props={{ entityType, schema, data: entity, cancelLabel: "Clear changes" }}
/>
),
icon: editIcon,
},
],
items: tabs(),
}}
/>

Expand Down
22 changes: 20 additions & 2 deletions src/Entities/Template/EntityList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ const sortTypes = props.sortTypes ?? [
{ text: "Oldest Updates", value: "{ updated_at: asc }" },
];

const initialTagFilter = Storage.get("global-tag-filter");
if (initialTagFilter) {
Storage.set("global-tag-filter", null);
}
const [searchKey, setSearchKey] = useState("");
const [sort, setSort] = useState(sortTypes[0].value);
const [tagsFilter, setTagsFilter] = useState(initialTagFilter);
const [items, setItems] = useState({ list: [], total: 0 });
const [showCreateModal, setShowCreateModal] = useState(false);
const [activeItem, setActiveItem] = useState(null);
Expand All @@ -48,7 +53,7 @@ const onLoadReset = (newItems, totalItems) => {
const loadItemsUseState = (isResetOrPageNumber) => {
const loader = isResetOrPageNumber === true ? onLoadReset : onLoad;
const offset = isResetOrPageNumber === true ? 0 : items.list.length;
return loadItems(buildQueries(searchKey, sort), queryName, offset, collection, loader);
return loadItems(buildQueries(searchKey, sort, { tags: tagsFilter }), queryName, offset, collection, loader);
};
useEffect(() => {
if (debounceTimer) clearTimeout(debounceTimer);
Expand All @@ -66,7 +71,7 @@ useEffect(() => {
}, [searchKey]);
useEffect(() => {
loadItemsUseState(true);
}, [sort]);
}, [sort, tagsFilter]);

const Wrapper = styled.div`
display: flex;
Expand Down Expand Up @@ -238,6 +243,19 @@ return (
/>
</Search>
</div>
<div className="col-2">
<Widget
src="${REPL_ACCOUNT}/widget/Entities.Template.Forms.TagsEditor"
props={{
placeholder: "Filter by Tag",
value: tagsFilter,
setValue: setTagsFilter,
namespace: schema.namespace,
entityType: schema.entityType,
allowNew: false,
}}
/>
</div>
<div className="col">
<SortContainer>
<Sort>
Expand Down
2 changes: 1 addition & 1 deletion src/Entities/Template/EntitySummary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ return (
) : (
<i className="bi bi-star" />
)}{" "}
{starCount}
{entity.stars}
</Button>
),
}}
Expand Down
157 changes: 157 additions & 0 deletions src/Entities/Template/Forms/TagCloud.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const { loadItems, fetchGraphQL } = VM.require("${REPL_ACCOUNT}/widget/Entities.QueryApi.Client");
if (!loadItems) {
return <p>Loading modules...</p>;
}

const { namespace, entityType, onSelect, limit } = props;
const tagLimit = limit ?? 100;

const [items, setItems] = useState(null);
const [tags, setTags] = useState(value || []);

const user = "dataplatform_near";
const entityIndexer = "entities";
const tagsTable = "tags";
const collection = `${user}_${entityIndexer}_${tagsTable}`;

const buildQueries = () => {
const namespaceClause = namespace ? `namespace: {_eq: "${namespace}"}` : "";
const entityTypeClause = entityType ? `entity_type: {_eq: "${entityType}"}` : "";
const whereClause = namespace || entityType ? `where: {${namespaceClause}, ${entityTypeClause}}` : "";

return `
query ListQuery($offset: Int, $limit: Int) {
${collection}(
${whereClause}
order_by: {count: desc, tag: asc},
offset: $offset, limit: $limit) {
namespace
entity_type
tag
count
}
${collection}_aggregate (
${whereClause}
){
aggregate {
count
}
}
}
`;
};
const queryName = "ListQuery";

const onLoad = (newItems, totalItems) => {
// no paging, just top tags
setItems({ list: newItems ?? [], total: totalItems });
};
loadItems(buildQueries(), queryName, 0, collection, onLoad, tagLimit);
if (items === null) {
return "Loading tags";
}

const Wrapper = styled.div`
position: relative;
${(p) =>
p.scroll
? `
ul {
padding-right: 16px;
}
&::after {
content: '';
display: block;
height: 100%;
width: 16px;
background: linear-gradient(to right, rgba(255,255,255,0), rgba(255,255,255,1));
position: absolute;
top: 0;
right: 0;
}
ul {
flex-wrap: nowrap;
}
`
: ""}
`;

const Tags = styled.ul`
display: flex;
flex-wrap: wrap;
list-style: none;
gap: 6px;
overflow: auto;
margin: 0;
padding: 0;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
`;

const Tag = styled.li`
padding: 3px 6px;
border-radius: 6px;
font-size: 12px;
line-height: 12px;
border: 1px solid currentcolor;
border-color: ${(p) => (p.primary ? "currentcolor" : "#E6E8EB")};
color: ${(p) => (p.primary ? "#26A65A" : "#687076")};
font-weight: 500;
white-space: nowrap;
`;

const Count = styled.span`
font-family: var(--font-mono);
color: #687076;
margin-left: 6px;
`;
const TooltipTag = styled.span`
font-family: var(--font-mono);
font-weight: bold;
margin-left: 4px;
`;

const selectTag = (tag) => {
if (onSelect) {
onSelect(tag);
}
};
const humanize = (str) => {
if (!str) return "";
return str.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
};

return (
<div>
<p>Filter by Tag</p>
<Wrapper scroll={props.scroll}>
<Tags>
{items && items.list
? items.list.map((tag, i) => (
<Widget
src="${REPL_ACCOUNT}/widget/DIG.Tooltip"
props={{
content: (
<span>
{humanize(tag.entity_type)}s with Tag <TooltipTag> {tag.tag}</TooltipTag>
</span>
),
trigger: (
<Tag key={i} onClick={() => selectTag(tag)}>
{tag.tag} <Count>{tag.count}</Count>
</Tag>
),
}}
/>
))
: ""}
</Tags>
</Wrapper>
</div>
);
3 changes: 2 additions & 1 deletion src/Entities/Template/Forms/TagsEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ if (!loadItems) {
const { namespace, entityType, value, setValue } = props;

const placeholder = props.placeholder ?? "Tags";
const allowNew = props.allowNew ?? true;

const [items, setItems] = useState(null);
const [tags, setTags] = useState(value || []);
Expand Down Expand Up @@ -89,7 +90,7 @@ return (
placeholder={placeholder}
selected={tags ?? []}
positionFixed
allowNew
allowNew={allowNew}
/>
</>
);
45 changes: 40 additions & 5 deletions src/Entities/Template/GenericEntityConfig.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ if (!href) {
return <></>;
}

const { namespace, entityType, title, schemaFile, homeLink } = props;
const { namespace, entityType, schemaFile } = props; // data props
const { title, homeLink } = props; // display props

const schemaLocation = schemaFile ? schemaFile : `${REPL_ACCOUNT}/widget/Entities.Template.GenericSchema`;
const { genSchema } = VM.require(schemaLocation);
if (!genSchema) {
Expand All @@ -21,12 +23,45 @@ const dataFields = Object.keys(schema).filter((key) => typeof schema[key] === "o
const standardFields = ["id", "accountId", "name", "displayName", "logoUrl", "attributes"];
const attributeFields = dataFields.filter((key) => !standardFields.includes(key));
const ns = namespace ? namespace : "default";
const buildQueries = (searchKey, sort) => {
const queryFilter = searchKey ? `display_name: {_ilike: "%${searchKey}%"}` : "";
const buildQueries = (searchKey, sort, filters) => {
function arrayInPostgresForm(a) {
if (!a) return a;
try {
const stringArray = JSON.stringify(a);
return stringArray.replaceAll("[", "{").replaceAll("]", "}").replaceAll('"', '\\"');
} catch (error) {
console.error("Malformed array field - Error parsing JSON", error);
return "";
}
}

let queryFilter = searchKey ? `, display_name: {_ilike: "%${searchKey}%"}` : "";
if (filters) {
Object.keys(filters).forEach((key) => {
const filter = filters[key];
if (!key || !filter) return;
if (filter) {
switch (key) {
case "tags":
if (filter && filter.length > 0) {
queryFilter += `, tags: {_contains: "${arrayInPostgresForm(filter)}"}`;
}
break;
case "stars":
queryFilter += `, stars: {_gte: ${filter}}`;
break;
default:
queryFilter += `, ${key}: {_eq: "${filter}"}`;
break;
}
}
});
}

return `
query ListQuery($offset: Int, $limit: Int) {
${collection}(
where: {namespace: {_eq: "${ns}"}, entity_type: {_eq: "${entityType}"}, ${queryFilter}}
where: {namespace: {_eq: "${ns}"}, entity_type: {_eq: "${entityType}"} ${queryFilter}}
order_by: [${sort} ],
offset: $offset, limit: $limit) {
entity_type
Expand Down Expand Up @@ -229,7 +264,7 @@ const defaultRenderTableItem = (rawItem, editFunction) => {
) : (
<i className="bi bi-star" />
)}{" "}
{starCount}
{item.stars}
</Button>
),
}}
Expand Down
Loading

0 comments on commit f7f8e2e

Please sign in to comment.