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
9 changes: 6 additions & 3 deletions app/client/src/components/DeleteConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DialogContentText,
DialogTitle
} from '@mui/material';
import { useTranslation } from 'react-i18next';

interface DeleteConfirmDialogProps {
open: boolean;
Expand All @@ -23,6 +24,8 @@ const DeleteConfirmDialog: React.FC<DeleteConfirmDialogProps> = ({
onConfirm,
onCancel
}) => {
const { t } = useTranslation(['common']);

return (
<Dialog
open={open}
Expand All @@ -38,13 +41,13 @@ const DeleteConfirmDialog: React.FC<DeleteConfirmDialogProps> = ({
</DialogContent>
)}
<DialogActions>
<Button onClick={onCancel}>Cancel</Button>
<Button onClick={onCancel}>{t('common:cancel')}</Button>
<Button onClick={onConfirm} autoFocus color="warning">
Delete
{t('common:delete')}
</Button>
</DialogActions>
</Dialog>
);
};

export default DeleteConfirmDialog;
export default DeleteConfirmDialog;
50 changes: 25 additions & 25 deletions app/client/src/components/Navigation/PrimaryNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -170,37 +170,37 @@ const MobileFeedsContent: React.FC<{ selectedNodeId: string }> = ({ selectedNode
const items: NavTreeViewItem[] = [];

view.folderFeedConnectors.forEach(folder => {
if (folder.connectorItems && folder.connectorItems.length > 0) {
const folderInboxCount = folder.connectorItems.reduce(
const connectorItems = folder.connectorItems || [];

if (folder.name) {
const folderInboxCount = connectorItems.reduce(
(sum, item) => sum + (item.inboxCount || 0),
0
);
allInboxCount += folderInboxCount;

if (folder.name) {
const folderItem: NavTreeViewItem = {
labelText: folder.name,
labelIcon: FolderOpenIcon,
linkTo: `/folder/${folder.id}`,
inboxCount: folderInboxCount,
childItems: folder.connectorItems.map(item => ({
labelText: item.name || '',
labelIcon: RssFeedIcon,
linkTo: `/connector/${item.id}`,
inboxCount: item.inboxCount,
})),
};
items.push(folderItem);
} else {
folder.connectorItems.forEach(item => {
items.push({
labelText: item.name || '',
labelIcon: RssFeedIcon,
linkTo: `/connector/${item.id}`,
inboxCount: item.inboxCount,
});
const folderItem: NavTreeViewItem = {
labelText: folder.name,
labelIcon: FolderOpenIcon,
linkTo: `/folder/${folder.id}`,
inboxCount: folderInboxCount,
childItems: connectorItems.map(item => ({
labelText: item.name || '',
labelIcon: RssFeedIcon,
linkTo: `/connector/${item.id}`,
inboxCount: item.inboxCount,
})),
};
items.push(folderItem);
} else {
connectorItems.forEach(item => {
items.push({
labelText: item.name || '',
labelIcon: RssFeedIcon,
linkTo: `/connector/${item.id}`,
inboxCount: item.inboxCount,
});
}
});
}
});

Expand Down
86 changes: 86 additions & 0 deletions app/client/src/components/SettingModal/FeedsSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder';
import UploadFileIcon from '@mui/icons-material/UploadFile';
import DownloadIcon from '@mui/icons-material/Download';
import SettingsIcon from '@mui/icons-material/Settings';
import DeleteSweepIcon from '@mui/icons-material/DeleteSweep';
import { styled } from "@mui/material/styles";
import FolderFormDialog from "./FolderFormDialog";
import FeedsFormDialog from "./FeedsFormDialog";
import { DragDropContext, Droppable, Draggable, DropResult } from 'react-beautiful-dnd';
import { reorder } from "../../common/arrayUtils";
import { useTranslation } from 'react-i18next';
import DeleteConfirmDialog from "../DeleteConfirmDialog";

interface TabPanelProps {
children?: React.ReactNode;
Expand Down Expand Up @@ -341,6 +343,8 @@ function FoldersTabContent() {
const [folderId, setFolderId] = React.useState<number>(0);
const [editFolderId, setEditFolderId] = React.useState<number>(null);
const [editFeedsId, setEditFeedsId] = React.useState<number>(null);
const [deleteFolderFeedsConfirmOpen, setDeleteFolderFeedsConfirmOpen] = React.useState(false);
const [deletingFolderFeeds, setDeletingFolderFeeds] = React.useState(false);
const { enqueueSnackbar } = useSnackbar();
const {
data: folders,
Expand All @@ -351,6 +355,9 @@ function FoldersTabContent() {
refetch: refetchConnectors
} = useQuery(["folder_connectors", folderId], async () => (await api.getSortedConnectorsByFolderIdUsingGET(folderId)).data);
const queryClient = useQueryClient();
const selectedFolder = folders?.find((folder) => (folder.id || 0) === folderId);
const folderFeedsCount = connectors?.length || 0;
const canDeleteFolderFeeds = Boolean(selectedFolder) && folderFeedsCount > 0;

function connectorDragEnd({ source, destination }: DropResult) {
if (!destination || !source || destination.index === source.index) {
Expand Down Expand Up @@ -390,6 +397,61 @@ function FoldersTabContent() {
});
}

async function handleDeleteFolderFeeds() {
if (!selectedFolder) {
setDeleteFolderFeedsConfirmOpen(false);
return;
}

const targetFolderName = selectedFolder.name || t('settings:noFolder');
const deletableConnectors = (connectors || []).filter((connector) => connector.id != null);

setDeleteFolderFeedsConfirmOpen(false);

if (deletableConnectors.length === 0) {
return;
}

setDeletingFolderFeeds(true);

try {
const results = await Promise.allSettled(
deletableConnectors.map((connector) => api.deleteFeedUsingPOST(connector.id))
);
const successCount = results.filter((result) => result.status === 'fulfilled').length;
const failedCount = results.length - successCount;

if (failedCount === 0) {
enqueueSnackbar(t('settings:folderFeedsDeleted', {
count: successCount,
name: targetFolderName
}), {
variant: "success",
anchorOrigin: { vertical: "bottom", horizontal: "center" }
});
} else if (successCount > 0) {
enqueueSnackbar(t('settings:folderFeedsPartiallyDeleted', {
successCount,
failedCount,
name: targetFolderName
}), {
variant: "warning",
anchorOrigin: { vertical: "bottom", horizontal: "center" }
});
} else {
enqueueSnackbar(t('settings:folderFeedsDeleteFailed', {
name: targetFolderName
}), {
variant: "error",
anchorOrigin: { vertical: "bottom", horizontal: "center" }
});
}
} finally {
await Promise.all([refetchConnectors(), refetchFolders()]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await Promise.all([refetchConnectors(), refetchFolders()]) can throw if either refetch fails, which would skip setDeletingFolderFeeds(false) and potentially leave the UI stuck in a deleting/disabled state. Consider handling refetch failures so the deleting flag is always cleared.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

setDeletingFolderFeeds(false);
}
}

return (
<div className={'pb-4'}>
<div className={'flex'}>
Expand Down Expand Up @@ -546,6 +608,20 @@ function FoldersTabContent() {
</Droppable>
</DragDropContext>
}
{
canDeleteFolderFeeds && (
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 2 }}>
<Button
color="warning"
startIcon={<DeleteSweepIcon />}
onClick={() => setDeleteFolderFeedsConfirmOpen(true)}
disabled={deletingFolderFeeds || folderFeedsCount === 0}
>
{t('settings:deleteFolderFeeds')}
</Button>
</Box>
)
}
</div>
</div>

Expand All @@ -561,6 +637,16 @@ function FoldersTabContent() {
refetchConnectors();
}} />
}
<DeleteConfirmDialog
open={deleteFolderFeedsConfirmOpen}
title={t('settings:deleteFolderFeeds')}
content={t('settings:deleteFolderFeedsConfirmDesc', {
count: folderFeedsCount,
name: selectedFolder?.name || t('settings:noFolder')
})}
onConfirm={handleDeleteFolderFeeds}
onCancel={() => setDeleteFolderFeedsConfirmOpen(false)}
/>
</div>
);
}
6 changes: 6 additions & 0 deletions app/client/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,18 @@
"addFolder": "Add Folder",
"editFolder": "Edit Folder",
"deleteFolder": "Delete Folder",
"deleteFolderFeeds": "Delete All Feeds in Folder",
"folderSaved": "Folder saved.",
"folderSaveFailed": "Failed to save folder.",
"folderDeleted": "Folder deleted.",
"folderDeleteFailed": "Failed to delete folder.",
"deleteFolderConfirmDesc": "Feeds under folder \"{{name}}\" will move to the root folder. Do you want to delete it?",
"deleteFolderFeedsConfirmDesc": "Articles in library will not be deleted. Do you want to delete all {{count}} feeds in folder \"{{name}}\"?",
"deleteFeedConfirmDesc": "Articles in library will not be deleted. Do you want to delete this feed \"{{name}}\"?",
"folderFeedsDeleted_one": "Deleted {{count}} feed from \"{{name}}\".",
"folderFeedsDeleted_other": "Deleted {{count}} feeds from \"{{name}}\".",
"folderFeedsPartiallyDeleted": "Deleted {{successCount}} feeds from \"{{name}}\", but {{failedCount}} failed.",
"folderFeedsDeleteFailed": "Failed to delete feeds from \"{{name}}\".",
"noFolder": "No Folder",
"moveTo": "Move to",
"githubToken": "GitHub Token",
Expand Down
10 changes: 8 additions & 2 deletions app/client/src/i18n/locales/zh-CN/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,19 @@
"addFolder": "添加文件夹",
"editFolder": "编辑文件夹",
"deleteFolder": "删除文件夹",
"deleteFolderFeeds": "删除该文件夹下全部订阅",
"folderSaved": "文件夹已保存。",
"folderSaveFailed": "保存文件夹失败。",
"folderDeleted": "文件夹已删除。",
"folderDeleteFailed": "删除文件夹失败。",
"deleteFolderConfirmDesc": "文件夹“{{name}}”下的订阅将移动到根目录。确定要删除它吗?",
"deleteFolderFeedsConfirmDesc": "收藏中的文章不会被删除。确定要删除文件夹“{{name}}”下全部 {{count}} 个订阅吗?",
"deleteFeedConfirmDesc": "收藏中的文章不会被删除。确定要删除订阅“{{name}}”吗?",
"noFolder": "无文件夹",
"folderFeedsDeleted_one": "已从文件夹“{{name}}”删除 {{count}} 个订阅。",
"folderFeedsDeleted_other": "已从文件夹“{{name}}”删除 {{count}} 个订阅。",
"folderFeedsPartiallyDeleted": "已从文件夹“{{name}}”删除 {{successCount}} 个订阅,另有 {{failedCount}} 个删除失败。",
"folderFeedsDeleteFailed": "删除文件夹“{{name}}”下的订阅失败。",
"noFolder": "根文件夹",
"moveTo": "移动到",
"githubToken": "GitHub 令牌",
"githubRepo": "仓库",
Expand Down Expand Up @@ -228,4 +234,4 @@
"shortcutsSelected": "已选 {{count}} / {{total}}",
"selectAll": "全选",
"deselectAll": "取消全选"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -157,11 +156,14 @@ private List<FolderConnectors> getFolderFeedConnectors(List<Folder> folders, Lis

private void fillFolderConnectors(List<FolderConnectors> folderConnectorsList, Folder folder,
List<Connector> childConnectors) {
if (!CollectionUtils.isEmpty(childConnectors)) {
boolean isPersistedFolder = folder.getId() != null;
if (isPersistedFolder || !childConnectors.isEmpty()) {
FolderConnectors folderConnectors = new FolderConnectors();
folderConnectors.setId(folder.getId());
folderConnectors.setName(folder.getName());
folderConnectors.setConnectorItems(toConnectorItems(childConnectors));
folderConnectors.setConnectorItems(childConnectors.isEmpty()
? Collections.emptyList()
: toConnectorItems(childConnectors));
folderConnectorsList.add(folderConnectors);
}
}
Expand Down
Loading