Skip to content

Commit edd07a9

Browse files
kpatl1Kishan Patelscnwwu
authored
Added feature to check for unsaved files in a folder upon deletion. (#1559)
* Needs testing with live connection. But should fix error. * added feature to check for unsaved files in folder * undo changes on diff file * Update ContentDataProvider.ts * Update ContentDataProvider.ts * DCO Remediation Commit for Kishan Patel <[email protected]> I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: ae98ac4 I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 774bc8e I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 4b8f211 Signed-off-by: Kishan Patel <[email protected]> * testing a new fix within the UI and provider. * removed code from SASContentAdapter and added it to UI and Service layer. * DCO Remediation Commit for Kishan Patel <[email protected]> I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 877ec93 I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 1bf8187 Signed-off-by: Kishan Patel <[email protected]> * changed to BFS with conccurrrent HTTP requests. * changes to reduce HTTP requests. needs fixing for depth > 1 * needs upward traversal fix * DCO Remediation Commit for Kishan Patel <[email protected]> I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 5ef4032 I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 3195f1d I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 9166f84 Signed-off-by: Kishan Patel <[email protected]> * debug * Update client/src/components/ContentNavigator/ContentDataProvider.ts Co-authored-by: Wei Wu <[email protected]> * Update client/src/components/ContentNavigator/ContentDataProvider.ts Co-authored-by: Wei Wu <[email protected]> * Update client/src/components/ContentNavigator/ContentDataProvider.ts Co-authored-by: Wei Wu <[email protected]> * debug statment removal * DCO Remediation Commit for Kishan Patel <[email protected]> I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 5da1aa5 Signed-off-by: Kishan Patel <[email protected]> * prettier formatting. * linting * Update index.ts * feat(ContentNavigator): remove changes from package lock * Merge branch 'main' into kibpat_VSExt_1136 * DCO Remediation Commit for Kishan Patel <[email protected]> I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 5da1aa5 Signed-off-by: Kishan Patel <[email protected]> Signed-off-by: Kishan Patel <[email protected]> * DCO Remediation Commit for Kishan Patel <[email protected]> I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 2c3e6e2 I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: ed1cf13 I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: b8dbf88 I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 0cba768 I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: d0c8028 Signed-off-by: Kishan Patel <[email protected]> * feat(ContentNavigator): resolved comments Signed-off-by: Kishan Patel <[email protected]> * feat(ContentNavigator): fixed uriToParentMap logic Signed-off-by: Kishan Patel <[email protected]> * DCO Remediation Commit for Kishan Patel <[email protected]> I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 5da1aa5 Signed-off-by: Kishan Patel <[email protected]> * feat(ContentNavigator): fixed while loop condition * fix(ContentNavigator) added check for dirty notebooks * DCO Remediation Commit for Kishan Patel <[email protected]> I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 7b7c95b I, Kishan Patel <[email protected]>, hereby add my Signed-off-by to this commit: 597cb50 Signed-off-by: Kishan Patel <[email protected]> * feat(ContentNavigator): removed fallback to allow for more graceful handling Signed-off-by: Kishan Patel <[email protected]> --------- Signed-off-by: Kishan Patel <[email protected]> Signed-off-by: Kishan Patel <[email protected]> Co-authored-by: Kishan Patel <[email protected]> Co-authored-by: Wei Wu <[email protected]>
1 parent 107895c commit edd07a9

File tree

3 files changed

+127
-1
lines changed

3 files changed

+127
-1
lines changed

client/src/components/ContentNavigator/ContentDataProvider.ts

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { basename, join } from "path";
3636
import { promisify } from "util";
3737

3838
import { profileConfig } from "../../commands/profile";
39+
import { getResourceId } from "../../connection/rest/util";
3940
import { SubscriptionProvider } from "../SubscriptionProvider";
4041
import { ViyaProfile } from "../profile";
4142
import { ContentModel } from "./ContentModel";
@@ -45,6 +46,7 @@ import {
4546
ROOT_FOLDER_TYPE,
4647
SERVER_HOME_FOLDER_TYPE,
4748
SERVER_ROOT_FOLDER_TYPE,
49+
STOP_SIGN,
4850
TRASH_FOLDER_TYPE,
4951
} from "./const";
5052
import {
@@ -79,6 +81,8 @@ class ContentDataProvider
7981
public dropMimeTypes: string[];
8082
public dragMimeTypes: string[];
8183

84+
private uriToParentMap = new Map<string, string>();
85+
8286
get treeView(): TreeView<ContentItem> {
8387
return this._treeView;
8488
}
@@ -214,6 +218,12 @@ class ContentDataProvider
214218
const isContainer = getIsContainer(item);
215219
const uri = await this.model.getUri(item, false);
216220

221+
// Cache the URI to parent mapping
222+
this.uriToParentMap.set(
223+
item.uri,
224+
item.parentFolderUri ? item.parentFolderUri : STOP_SIGN,
225+
);
226+
217227
return {
218228
collapsibleState: isContainer
219229
? TreeItemCollapsibleState.Collapsed
@@ -444,6 +454,7 @@ class ContentDataProvider
444454

445455
public refresh(): void {
446456
this._onDidChangeTreeData.fire(undefined);
457+
this.uriToParentMap.clear();
447458
}
448459

449460
public async getParent(
@@ -505,6 +516,102 @@ class ContentDataProvider
505516
}
506517
}
507518

519+
public async checkFolderDirty(resource: ContentItem): Promise<boolean> {
520+
if (!resource.vscUri) {
521+
return false;
522+
}
523+
524+
const targetFolderUri = resource.uri;
525+
526+
// Check for dirty text documents
527+
const dirtyTextFiles = workspace.textDocuments
528+
.filter((doc) => {
529+
if (!doc.isDirty) {
530+
return false;
531+
}
532+
533+
const scheme = doc.uri.scheme;
534+
return (
535+
scheme === "sasContent" ||
536+
scheme === "sasServer" ||
537+
scheme === "sasContentReadOnly" ||
538+
scheme === "sasServerReadOnly"
539+
);
540+
})
541+
.map((doc) => doc.uri);
542+
543+
// Check for dirty notebook documents (SASNB files)
544+
const dirtyNotebookFiles = workspace.notebookDocuments
545+
.filter((notebook) => {
546+
if (!notebook.isDirty) {
547+
return false;
548+
}
549+
550+
const scheme = notebook.uri.scheme;
551+
return (
552+
scheme === "sasContent" ||
553+
scheme === "sasServer" ||
554+
scheme === "sasContentReadOnly" ||
555+
scheme === "sasServerReadOnly"
556+
);
557+
})
558+
.map((notebook) => notebook.uri);
559+
560+
const allDirtyFiles = [...dirtyTextFiles, ...dirtyNotebookFiles];
561+
562+
if (allDirtyFiles.length === 0) {
563+
return false;
564+
}
565+
566+
for (const dirtyFileUri of allDirtyFiles) {
567+
if (
568+
await this.isDescendantOf(getResourceId(dirtyFileUri), targetFolderUri)
569+
) {
570+
return true;
571+
}
572+
}
573+
574+
return false;
575+
}
576+
577+
private async isDescendantOf(
578+
fileUri: string,
579+
ancestorFolderUri: string,
580+
): Promise<boolean> {
581+
let currentParentUri = this.uriToParentMap.get(fileUri);
582+
583+
// If the cache doesn't contain the uri, it's acceptable to not pop up
584+
// This can happen when switching Viya profiles where the dirty file
585+
// is from a different server context
586+
if (!currentParentUri) {
587+
return false;
588+
}
589+
590+
let depth = 0;
591+
while (currentParentUri || depth <= 10) {
592+
if (currentParentUri === ancestorFolderUri) {
593+
return true;
594+
}
595+
596+
if (currentParentUri === STOP_SIGN) {
597+
return false;
598+
}
599+
600+
const nextParentUri = this.uriToParentMap.get(currentParentUri);
601+
602+
if (nextParentUri) {
603+
currentParentUri = nextParentUri;
604+
} else {
605+
// If the cache doesn't contain the parent uri, stop traversing
606+
// rather than making a server call which could be for a different server
607+
break;
608+
}
609+
depth++;
610+
}
611+
612+
return false;
613+
}
614+
508615
public async downloadContentItems(
509616
folderUri: Uri,
510617
selections: ContentItem[],
@@ -759,6 +866,5 @@ const closeFileIfOpen = (item: ContentItem): Promise<Uri[]> | boolean => {
759866
.catch(reject);
760867
});
761868
}
762-
763869
return true;
764870
};

client/src/components/ContentNavigator/const.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const SAS_SERVER_ROOT_FOLDER = createStaticFolder(
2626
"getDirectoryMembers",
2727
);
2828

29+
export const STOP_SIGN = "__STOP_SIGN__";
2930
export const FILE_TYPE = "file";
3031
export const DATAFLOW_TYPE = "dataFlow";
3132
export const FILE_TYPES = [FILE_TYPE, DATAFLOW_TYPE];
@@ -62,9 +63,13 @@ export const Messages = {
6263
AddFileToMyFolderSuccess: l10n.t("File added to my folder."),
6364
AddToFavoritesError: l10n.t("The item could not be added to My Favorites."),
6465
DeleteButtonLabel: l10n.t("Delete"),
66+
MoveToRecycleBinLabel: l10n.t("Move to Recycle Bin"),
6567
DeleteWarningMessage: l10n.t(
6668
'Are you sure you want to permanently delete the item "{name}"?',
6769
),
70+
RecycleDirtyFolderWarning: l10n.t(
71+
"This folder contains unsaved files, are you sure you want to delete?",
72+
),
6873
EmptyRecycleBinError: l10n.t("Unable to empty the recycle bin."),
6974
EmptyRecycleBinWarningMessage: l10n.t(
7075
"Are you sure you want to permanently delete all the items? You cannot undo this action.",

client/src/components/ContentNavigator/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ class ContentNavigator implements SubscriptionProvider {
107107
return;
108108
}
109109
const isContainer = getIsContainer(resource);
110+
const hasUnsavedFiles = isContainer
111+
? await this.contentDataProvider.checkFolderDirty(resource)
112+
: false;
110113
const moveToRecycleBin =
111114
this.contentDataProvider.canRecycleResource(resource);
112115

@@ -121,6 +124,18 @@ class ContentNavigator implements SubscriptionProvider {
121124
))
122125
) {
123126
return;
127+
} else if (moveToRecycleBin && hasUnsavedFiles) {
128+
if (
129+
!(await window.showWarningMessage(
130+
l10n.t(Messages.RecycleDirtyFolderWarning, {
131+
name: resource.name,
132+
}),
133+
{ modal: true },
134+
Messages.MoveToRecycleBinLabel,
135+
))
136+
) {
137+
return;
138+
}
124139
}
125140
const deleteResult = moveToRecycleBin
126141
? await this.contentDataProvider.recycleResource(resource)

0 commit comments

Comments
 (0)