Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions public/reearth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ extensions:
label: CMS Data Visualizer Server
- key: cms_integration_api
label: CMS Integration API
- key: cms_public_api
label: CMS Public API
- id: server_base_url
title: Server Base URL
type: string
Expand Down Expand Up @@ -76,6 +78,43 @@ extensions:
field: data_source_type
type: string
value: cms_integration_api
- id: public_api_base_url
title: Public API Base URL
type: string
availableIf:
field: data_source_type
type: string
value: cms_public_api
- id: cms_workspace_id_for_public_api
title: CMS Workspace ID
type: string
availableIf:
field: data_source_type
type: string
value: cms_public_api
- id: cms_project_id_for_public_api
title: CMS Project ID
type: string
availableIf:
field: data_source_type
type: string
value: cms_public_api
- id: cms_model_id_for_public_api
title: CMS Model ID
type: string
availableIf:
field: data_source_type
type: string
value: cms_public_api
- id: value_filters_for_public_api
title: Filters
description: "Optional, specify value filters, Example: status===published|reviewed;category===news"
type: string
ui: multiline
availableIf:
field: data_source_type
type: string
value: cms_public_api
- id: visualization
title: Visualization
fields:
Expand Down
4 changes: 4 additions & 0 deletions src/extensions/inspector_block/main/PropertyValue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ const PropertyValue: FC<Props> = ({ property }) => {
<div className="whitespace-pre-wrap break-words">
{new Date(property.value as string).toLocaleString()}
</div>
) : property.type === "object" ? (
<div className="whitespace-pre-wrap break-words">
{JSON.stringify(property.value)}
</div>
) : (
<div className="whitespace-pre-wrap break-words">
{property.value?.toString()}
Expand Down
248 changes: 53 additions & 195 deletions src/extensions/visualizer/main/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,48 @@
import { useEffect } from "react";

import { getItemsFromIntegrationAPI } from "./utils/getItemsFromIntegrationAPI";
import { getItemsFromPublicAPI } from "./utils/getItemsFromPublicAPI";
import { getItemsFromServer } from "./utils/getItemsFromServer";

import { postMsg } from "@/shared/utils";

type WidgetProperty = {
api: {
data_source_type?: "cms_data_visualizer_server" | "cms_integration_api";
// for get items from server
server_base_url?: string;
server_api_key?: string;
// for get items from integration API
integration_api_base_url?: string;
integration_api_key?: string;
cms_workspace_id?: string;
cms_project_id?: string;
cms_model_id?: string;
value_filters?: string;
// for get items from public API
public_api_base_url?: string;
cms_workspace_id_for_public_api?: string;
cms_project_id_for_public_api?: string;
cms_model_id_for_public_api?: string;
value_filters_for_public_api?: string;
};
appearance: {
marker_appearance?: string;
};
};

type Schema = {
id: string;
key: string;
multiple: boolean;
name: string;
required: boolean;
type: string;
}[];

// Sub-collect Asset type
type Asset = {
id: string;
url: string;
};

type Item = {
export type Item = {
id: string;
fields: Field[];
};

type Field = {
export type Field = {
id: string;
key: string;
type: string;
value: unknown;
group?: string;
name?: string;
};

export default () => {
Expand Down Expand Up @@ -74,24 +73,15 @@ export default () => {
);
return;
}

try {
const url = `${widgetProperty.api.server_base_url}/items`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${widgetProperty.api.server_api_key}`,
},
const items = await getItemsFromServer({
baseUrl: widgetProperty.api.server_base_url,
apiKey: widgetProperty.api.server_api_key,
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
postMsg("addLayer", data.data.items || []);
postMsg("addLayer", items);
} catch (error) {
console.error("Error fetching data:", error);
console.error("Error fetching data from server:", error);
}
} else if (
// Fetch data from CMS Integration API
Expand All @@ -109,177 +99,45 @@ export default () => {
);
return;
}

// Fetch Assets with pagination
let assets: Asset[];
try {
const baseUrl = `${widgetProperty.api.integration_api_base_url}/${widgetProperty.api.cms_workspace_id}/projects/${widgetProperty.api.cms_project_id}/assets`;

const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${widgetProperty.api.integration_api_key}`,
};
// First request to get total count
const firstResponse = await fetch(`${baseUrl}?perPage=100&page=1`, {
method: "GET",
headers,
const items = await getItemsFromIntegrationAPI({
baseUrl: widgetProperty.api.integration_api_base_url,
apiKey: widgetProperty.api.integration_api_key,
workspaceId: widgetProperty.api.cms_workspace_id,
projectId: widgetProperty.api.cms_project_id,
modelId: widgetProperty.api.cms_model_id,
valueFilters: widgetProperty.api.value_filters,
});

if (!firstResponse.ok) {
throw new Error(`HTTP error! status: ${firstResponse.status}`);
}

const firstData = await firstResponse.json();
const totalCount = firstData.totalCount;
const perPage = 100;
const totalPages = Math.ceil(totalCount / perPage);

assets = firstData.items || [];

// Fetch remaining pages if there are more
if (totalPages > 1) {
const pagePromises = [];
for (let page = 2; page <= totalPages; page++) {
pagePromises.push(
fetch(`${baseUrl}?perPage=${perPage}&page=${page}`, {
method: "GET",
headers,
})
);
}

const responses = await Promise.all(pagePromises);

for (const response of responses) {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
assets = assets.concat(data.items || []);
}
}
postMsg("addLayer", items);
} catch (error) {
console.error("Error fetching assets:", error);
console.error("Error fetching data from CMS integration API:", error);
}

// Fetch Schema
let schema: Schema;
try {
const url = `${widgetProperty.api.integration_api_base_url}/${widgetProperty.api.cms_workspace_id}/projects/${widgetProperty.api.cms_project_id}/models/${widgetProperty.api.cms_model_id}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${widgetProperty.api.integration_api_key}`,
},
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
schema = data.schema.fields;
} catch (error) {
console.error("Error fetching schema:", error);
} else if (
// Fetch data from CMS Public API
widgetProperty.api.data_source_type === "cms_public_api"
) {
if (
!widgetProperty.api.public_api_base_url ||
!widgetProperty.api.cms_workspace_id_for_public_api ||
!widgetProperty.api.cms_project_id_for_public_api ||
!widgetProperty.api.cms_model_id_for_public_api
) {
console.warn(
"Please set the Public API Base URL, CMS Workspace ID, CMS Project ID, and CMS Model ID in the widget properties."
);
return;
}

// Fetch Items with pagination
try {
const baseUrl = `${widgetProperty.api.integration_api_base_url}/${widgetProperty.api.cms_workspace_id}/projects/${widgetProperty.api.cms_project_id}/models/${widgetProperty.api.cms_model_id}/items`;

const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${widgetProperty.api.integration_api_key}`,
};

// First request to get total count
const firstResponse = await fetch(`${baseUrl}?perPage=100&page=1`, {
method: "GET",
headers,
const items = await getItemsFromPublicAPI({
baseUrl: widgetProperty.api.public_api_base_url,
workspaceId: widgetProperty.api.cms_workspace_id_for_public_api,
projectId: widgetProperty.api.cms_project_id_for_public_api,
modelId: widgetProperty.api.cms_model_id_for_public_api,
valueFilters: widgetProperty.api.value_filters_for_public_api,
});

if (!firstResponse.ok) {
throw new Error(`HTTP error! status: ${firstResponse.status}`);
}

const firstData = await firstResponse.json();
const totalCount = firstData.totalCount;
const perPage = 100;
const totalPages = Math.ceil(totalCount / perPage);

let allItems: Item[] = firstData.items || [];

// Fetch remaining pages if there are more
if (totalPages > 1) {
const pagePromises = [];
for (let page = 2; page <= totalPages; page++) {
pagePromises.push(
fetch(`${baseUrl}?perPage=${perPage}&page=${page}`, {
method: "GET",
headers,
})
);
}

const responses = await Promise.all(pagePromises);

for (const response of responses) {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
allItems = allItems.concat(data.items || []);
}
}

// append schema's name to each item
// replace asset id with asset url in each item
allItems = allItems.map((item: Item) => ({
...item,
fields: [
...item.fields.map((field: Field) => ({
...field,
name: schema.find((s) => s.key === field.key)?.name,
value:
field.type === "asset" && typeof field.value === "string"
? assets.find((a) => a.id === field.value)?.url ||
field.value
: field.type === "asset" && Array.isArray(field.value)
? (field.value as string[])
.map(
(assetId) =>
assets.find((a) => a.id === assetId)?.url ||
assetId
)
.reverse()
: field.value,
})),
],
}));

// Apply value filters if any
// Example: status===published|reviewed;category===news
if (widgetProperty.api.value_filters) {
const filters = widgetProperty.api.value_filters
.split(";")
.map((filter) => {
const [key, values] = filter.split("===");
return { key, values: values.split("|") };
});

allItems = allItems.filter((item) =>
filters.every((filter) => {
const field = item.fields.find((f) => f.key === filter.key);
if (!field) return false;
return filter.values.includes(String(field.value));
})
);
}

postMsg("addLayer", allItems);
postMsg("addLayer", items);
} catch (error) {
console.error("Error fetching data:", error);
console.error("Error fetching data from CMS public API:", error);
}
}
}
Expand Down
Loading