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
74 changes: 74 additions & 0 deletions ui/src/components/ExportButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useState } from "react";
import {
EuiButton,
EuiPopover,
EuiContextMenuPanel,
EuiContextMenuItem,
} from "@elastic/eui";

interface ExportButtonProps {
data: any[];
fileName: string;
formats?: ("json" | "csv")[];
}

const ExportButton: React.FC<ExportButtonProps> = ({
data,
fileName,
formats = ["json", "csv"],
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);

const exportData = (format: "json" | "csv") => {
let content = "";
let mimeType = "";

if (format === "json") {
content = JSON.stringify(data, null, 2);
mimeType = "application/json";
} else {
const headers = Object.keys(data[0] || {}).join(",") + "\n";
const rows = data.map((item) => Object.values(item).join(",")).join("\n");
content = headers + rows;
mimeType = "text/csv";
}

const blob = new Blob([content], { type: mimeType });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = `${fileName}.${format}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};

const exportMenu = (
<EuiContextMenuPanel
items={formats.map((format) => (
<EuiContextMenuItem key={format} onClick={() => exportData(format)}>
Export {format.toUpperCase()}
</EuiContextMenuItem>
))}
/>
);

return (
<EuiPopover
button={
<EuiButton
color="primary"
fill
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
>
Export
</EuiButton>
}
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(false)}
panelPaddingSize="s"
>
{exportMenu}
</EuiPopover>
);
};
export default ExportButton;
8 changes: 8 additions & 0 deletions ui/src/pages/data-sources/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import DataSourceIndexEmptyState from "./DataSourceIndexEmptyState";
import { DataSourceIcon } from "../../graphics/DataSourceIcon";
import { useSearchQuery } from "../../hooks/useSearchInputWithTags";
import { feast } from "../../protos";
import ExportButton from "../../components/ExportButton";

const useLoadDatasources = () => {
const registryUrl = useContext(RegistryPathContext);
Expand Down Expand Up @@ -65,6 +66,13 @@ const Index = () => {
restrictWidth
iconType={DataSourceIcon}
pageTitle="Data Sources"
rightSideItems={[
<ExportButton
data={filterResult ?? []}
fileName="data_sources"
formats={["json"]}
/>,
]}
/>
<EuiPageTemplate.Section>
{isLoading && (
Expand Down
8 changes: 8 additions & 0 deletions ui/src/pages/entities/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import EntitiesListingTable from "./EntitiesListingTable";
import { useDocumentTitle } from "../../hooks/useDocumentTitle";
import RegistryPathContext from "../../contexts/RegistryPathContext";
import EntityIndexEmptyState from "./EntityIndexEmptyState";
import ExportButton from "../../components/ExportButton";

const useLoadEntities = () => {
const registryUrl = useContext(RegistryPathContext);
Expand Down Expand Up @@ -36,6 +37,13 @@ const Index = () => {
restrictWidth
iconType={EntityIcon}
pageTitle="Entities"
rightSideItems={[
<ExportButton
data={data ?? []}
fileName="entities"
formats={["json"]}
/>,
]}
/>
<EuiPageTemplate.Section>
{isLoading && (
Expand Down
8 changes: 8 additions & 0 deletions ui/src/pages/feature-services/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useDocumentTitle } from "../../hooks/useDocumentTitle";
import RegistryPathContext from "../../contexts/RegistryPathContext";
import FeatureServiceIndexEmptyState from "./FeatureServiceIndexEmptyState";
import TagSearch from "../../components/TagSearch";
import ExportButton from "../../components/ExportButton";
import { useFeatureServiceTagsAggregation } from "../../hooks/useTagsAggregation";
import { feast } from "../../protos";

Expand Down Expand Up @@ -115,6 +116,13 @@ const Index = () => {
restrictWidth
iconType={FeatureServiceIcon}
pageTitle="Feature Services"
rightSideItems={[
<ExportButton
data={filterResult ?? []}
fileName="feature_services"
formats={["json"]}
/>,
]}
/>
<EuiPageTemplate.Section>
{isLoading && (
Expand Down
8 changes: 8 additions & 0 deletions ui/src/pages/feature-views/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import RegistryPathContext from "../../contexts/RegistryPathContext";
import FeatureViewIndexEmptyState from "./FeatureViewIndexEmptyState";
import { useFeatureViewTagsAggregation } from "../../hooks/useTagsAggregation";
import TagSearch from "../../components/TagSearch";
import ExportButton from "../../components/ExportButton";

const useLoadFeatureViews = () => {
const registryUrl = useContext(RegistryPathContext);
Expand Down Expand Up @@ -117,6 +118,13 @@ const Index = () => {
restrictWidth
iconType={FeatureViewIcon}
pageTitle="Feature Views"
rightSideItems={[
<ExportButton
data={filterResult ?? []}
fileName="feature_views"
formats={["json"]}
/>,
]}
/>
<EuiPageTemplate.Section>
{isLoading && (
Expand Down
4 changes: 4 additions & 0 deletions ui/src/pages/features/FeatureListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Pagination,
} from "@elastic/eui";
import EuiCustomLink from "../../components/EuiCustomLink";
import ExportButton from "../../components/ExportButton";
import { useParams } from "react-router-dom";
import useLoadRegistry from "../../queries/useLoadRegistry";
import RegistryPathContext from "../../contexts/RegistryPathContext";
Expand Down Expand Up @@ -109,6 +110,9 @@ const FeatureListPage = () => {
restrictWidth
iconType={FeatureIcon}
pageTitle="Feature List"
rightSideItems={[
<ExportButton data={filteredFeatures} fileName="features" />,
]}
/>
<EuiPageTemplate.Section>
{isLoading ? (
Expand Down