Connected object tags API (#421)

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2020-11-19 23:30:33 -06:00
committed by GitHub
parent 8cf678fb27
commit 8a6a75b7a2
3 changed files with 357 additions and 36 deletions

View File

@@ -0,0 +1,138 @@
import React, { useState } from "react";
import { Button, Grid } from "@material-ui/core";
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary";
import api from "../../../../../../common/api";
interface ITagModal {
modalOpen: boolean;
currentTags: any;
bucketName: string;
versionId: string;
onCloseAndUpdate: (refresh: boolean) => void;
selectedObject: string;
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
},
pathLabel: {
marginTop: 0,
marginBottom: 32,
},
...modalBasic,
});
const AddTagModal = ({
modalOpen,
currentTags,
selectedObject,
onCloseAndUpdate,
bucketName,
versionId,
classes,
}: ITagModal) => {
const [newKey, setNewKey] = useState<string>("");
const [newLabel, setNewLabel] = useState<string>("");
const [error, setError] = useState<string>("");
const [isSending, setIsSending] = useState<boolean>(false);
const resetForm = () => {
setNewLabel("");
setNewKey("");
};
const addTagProcess = () => {
setIsSending(true);
const newTag: any = {};
newTag[newKey] = newLabel;
const newTagList = { ...currentTags, ...newTag };
api
.invoke(
"PUT",
`/api/v1/buckets/${bucketName}/objects/tags?prefix=${selectedObject}&version_id=${versionId}`,
{ tags: newTagList }
)
.then((res: any) => {
setIsSending(false);
onCloseAndUpdate(true);
})
.catch((error) => {
setError(error);
setIsSending(false);
});
};
return (
<React.Fragment>
<ModalWrapper
modalOpen={modalOpen}
title="Add New Tag"
onClose={() => {
onCloseAndUpdate(false);
}}
>
<Grid container>
<h3 className={classes.pathLabel}>
Selected Object: {selectedObject}
</h3>
{error !== "" && <span>{error}</span>}
<Grid item xs={12}>
<InputBoxWrapper
value={newKey}
label={"New Tag Key"}
id={"newTagKey"}
name={"newTagKey"}
placeholder={"Enter New Tag Key"}
onChange={(e) => {
setNewKey(e.target.value);
}}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
value={newLabel}
label={"New Tag Label"}
id={"newTagLabel"}
name={"newTagLabel"}
placeholder={"Enter New Tag Label"}
onChange={(e) => {
setNewLabel(e.target.value);
}}
/>
</Grid>
<Grid item xs={12} className={classes.buttonContainer}>
<button
type="button"
color="primary"
className={classes.clearButton}
onClick={resetForm}
>
Clear
</button>
<Button
type="submit"
variant="contained"
color="primary"
disabled={
newLabel.trim() === "" || newKey.trim() === "" || isSending
}
onClick={addTagProcess}
>
Save
</Button>
</Grid>
</Grid>
</ModalWrapper>
</React.Fragment>
);
};
export default withStyles(styles)(AddTagModal);

View File

@@ -0,0 +1,125 @@
import React, { useState } from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Grid,
LinearProgress,
} from "@material-ui/core";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { modalBasic } from "../../../../Common/FormComponents/common/styleLibrary";
import api from "../../../../../../common/api";
import Typography from "@material-ui/core/Typography";
interface IDeleteTagModal {
deleteOpen: boolean;
currentTags: any;
bucketName: string;
versionId: string;
selectedTag: [string, string];
onCloseAndUpdate: (refresh: boolean) => void;
selectedObject: string;
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
},
pathLabel: {
marginTop: 0,
marginBottom: 32,
},
...modalBasic,
});
const DeleteTagModal = ({
deleteOpen,
currentTags,
selectedObject,
selectedTag,
onCloseAndUpdate,
bucketName,
versionId,
classes,
}: IDeleteTagModal) => {
const [deleteError, setDeleteError] = useState<string>("");
const [deleteLoading, setDeleteSending] = useState<boolean>(false);
const [tagKey, tagLabel] = selectedTag;
const removeTagProcess = () => {
setDeleteSending(true);
const cleanObject = { ...currentTags };
delete cleanObject[tagKey];
api
.invoke(
"PUT",
`/api/v1/buckets/${bucketName}/objects/tags?prefix=${selectedObject}&version_id=${versionId}`,
{ tags: cleanObject }
)
.then((res: any) => {
setDeleteSending(false);
onCloseAndUpdate(true);
})
.catch((error) => {
setDeleteError(error);
setDeleteSending(false);
});
};
return (
<Dialog
open={deleteOpen}
onClose={() => {
onCloseAndUpdate(false);
}}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Delete Tag</DialogTitle>
<DialogContent>
{deleteLoading && <LinearProgress />}
<DialogContentText id="alert-dialog-description">
Are you sure you want to delete the tag{" "}
<b className={classes.wrapText}>
{tagKey} : {tagLabel}
</b>{" "}
from {selectedObject}?
{deleteError !== "" && (
<React.Fragment>
<br />
<Typography
component="p"
variant="body1"
className={classes.errorBlock}
>
{deleteError}
</Typography>
</React.Fragment>
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={() => {
onCloseAndUpdate(false);
}}
color="primary"
disabled={deleteLoading}
>
Cancel
</Button>
<Button onClick={removeTagProcess} color="secondary" autoFocus>
Delete
</Button>
</DialogActions>
</Dialog>
);
};
export default withStyles(styles)(DeleteTagModal);

View File

@@ -48,6 +48,8 @@ import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
import DeleteObject from "../ListObjects/DeleteObject";
import { removeRouteLevel } from "../../../../ObjectBrowser/actions";
import { connect } from "react-redux";
import AddTagModal from "./AddTagModal";
import DeleteTagModal from "./DeleteTagModal";
const styles = (theme: Theme) =>
createStyles({
@@ -163,8 +165,12 @@ const ObjectDetails = ({
routesList,
removeRouteLevel,
}: IObjectDetailsProps) => {
const [loadObjectData, setLoadObjectData] = useState<boolean>(true);
const [shareFileModalOpen, setShareFileModalOpen] = useState<boolean>(false);
const [retentionModalOpen, setRetentionModalOpen] = useState<boolean>(false);
const [tagModalOpen, setTagModalOpen] = useState<boolean>(false);
const [deleteTagModalOpen, setDeleteTagModalOpen] = useState<boolean>(false);
const [selectedTag, setSelectedTag] = useState<[string, string]>(["", ""]);
const [actualInfo, setActualInfo] = useState<IFileInfo>(emptyFile);
const [versions, setVersions] = useState<IFileInfo[]>([]);
const [filterVersion, setFilterVersion] = useState<string>("");
@@ -178,22 +184,32 @@ const ObjectDetails = ({
const pathInBucket = allPathData.slice(3).join("/");
useEffect(() => {
api
.invoke(
"GET",
`/api/v1/buckets/${bucketName}/objects?prefix=${pathInBucket}&with_versions=true`
)
.then((res: IFileInfo[]) => {
const result = get(res, "objects", []);
setActualInfo(
result.find((el: IFileInfo) => el.is_latest) || emptyFile
);
setVersions(result.filter((el: IFileInfo) => !el.is_latest));
})
.catch((error) => {
setError(error);
});
}, []);
if (loadObjectData) {
api
.invoke(
"GET",
`/api/v1/buckets/${bucketName}/objects?prefix=${pathInBucket}&with_versions=true`
)
.then((res: IFileInfo[]) => {
const result = get(res, "objects", []);
setActualInfo(
result.find((el: IFileInfo) => el.is_latest) || emptyFile
);
setVersions(result.filter((el: IFileInfo) => !el.is_latest));
setLoadObjectData(false);
})
.catch((error) => {
setError(error);
setLoadObjectData(false);
});
}
}, [loadObjectData]);
let tagKeys: string[] = [];
if (actualInfo.tags) {
tagKeys = Object.keys(actualInfo.tags);
}
const openRetentionModal = () => {
setRetentionModalOpen(true);
@@ -215,8 +231,9 @@ const ObjectDetails = ({
console.log("close share modal");
};
const deleteTag = () => {
console.log("delete tag");
const deleteTag = (tagKey: string, tagLabel: string) => {
setSelectedTag([tagKey, tagLabel]);
setDeleteTagModalOpen(true);
};
const downloadObject = (path: string) => {
@@ -247,6 +264,22 @@ const ObjectDetails = ({
}
};
const closeAddTagModal = (reloadObjectData: boolean) => {
setTagModalOpen(false);
if (reloadObjectData) {
setLoadObjectData(true);
}
};
const closeDeleteTagModal = (reloadObjectData: boolean) => {
setDeleteTagModalOpen(false);
if (reloadObjectData) {
setLoadObjectData(true);
}
};
return (
<React.Fragment>
<PageHeader label={"Object Browser"} />
@@ -271,6 +304,27 @@ const ObjectDetails = ({
closeDeleteModalAndRefresh={closeDeleteModal}
/>
)}
{tagModalOpen && (
<AddTagModal
modalOpen={tagModalOpen}
currentTags={actualInfo.tags}
selectedObject={pathInBucket}
versionId={actualInfo.version_id}
bucketName={bucketName}
onCloseAndUpdate={closeAddTagModal}
/>
)}
{deleteTagModalOpen && (
<DeleteTagModal
deleteOpen={deleteTagModalOpen}
currentTags={actualInfo.tags}
selectedObject={pathInBucket}
versionId={actualInfo.version_id}
bucketName={bucketName}
onCloseAndUpdate={closeDeleteTagModal}
selectedTag={selectedTag}
/>
)}
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.obTitleSection}>
@@ -371,24 +425,25 @@ const ObjectDetails = ({
</Grid>
<Grid item xs={12} className={classes.tagsContainer}>
<div className={classes.tagText}>Tags:</div>
{actualInfo.tags &&
Object.keys(actualInfo.tags).map((itemKey, index) => {
const tag = get(actualInfo, `tags.${itemKey}`, "");
if (tag !== "") {
return (
<Chip
key={`chip-${index}`}
className={classes.tag}
size="small"
label={tag}
color="primary"
deleteIcon={<CloseIcon />}
onDelete={deleteTag}
/>
);
}
return null;
})}
{tagKeys.map((tagKey, index) => {
const tag = get(actualInfo, `tags.${tagKey}`, "");
if (tag !== "") {
return (
<Chip
key={`chip-${index}`}
className={classes.tag}
size="small"
label={`${tagKey} : ${tag}`}
color="primary"
deleteIcon={<CloseIcon />}
onDelete={() => {
deleteTag(tagKey, tag);
}}
/>
);
}
return null;
})}
<Chip
className={classes.tag}
icon={<AddIcon />}
@@ -397,6 +452,9 @@ const ObjectDetails = ({
label="Add tag"
color="primary"
variant="outlined"
onClick={() => {
setTagModalOpen(true);
}}
/>
</Grid>
<Grid item xs={12} className={classes.actionsTray}>