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
108 changes: 101 additions & 7 deletions src/dashboard/Data/Views/Views.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ class Views extends TableView {
}
if (!columns[key]) {
columns[key] = { type, width: Math.min(computeWidth(key), 200) };
} else if (type === 'Pointer' && columns[key].type !== 'Pointer') {
// If we find a pointer value, upgrade the column type to Pointer
columns[key].type = 'Pointer';
}
const width = computeWidth(val);
if (width > columns[key].width && columns[key].width < 200) {
Expand Down Expand Up @@ -550,12 +553,41 @@ class Views extends TableView {
}

renderHeaders() {
return this.state.order.map(({ name, width }, i) => (
<div key={name} className={styles.headerWrap} style={{ width }}>
{name}
<DragHandle className={styles.handle} onDrag={delta => this.handleResize(i, delta)} />
</div>
));
return this.state.order.map(({ name, width }, i) => {
const columnType = this.state.columns[name]?.type;
const isPointerColumn = columnType === 'Pointer';

return (
<div key={name} className={styles.headerWrap} style={{ width }}>
<span className={styles.headerText}>
<span className={styles.headerLabel}>{name}</span>
{isPointerColumn && (
<button
type="button"
className={styles.pointerIcon}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
this.handleOpenAllPointers(name);
// Remove focus after action to follow UX best practices
e.currentTarget.blur();
}}
aria-label={`Open all pointers in ${name} column in new tabs`}
title="Open all pointers in new tabs"
>
<Icon
name="right-outline"
width={20}
height={20}
fill="white"
/>
</button>
)}
</span>
<DragHandle className={styles.handle} onDrag={delta => this.handleResize(i, delta)} />
</div>
);
});
}

renderEmpty() {
Expand Down Expand Up @@ -823,14 +855,76 @@ class Views extends TableView {
`browser/${className}?filters=${encodeURIComponent(filters)}`,
true
),
'_blank'
'_blank',
'noopener,noreferrer'
);
}

handleValueClick(value) {
this.setState({ viewValue: value });
}

handleOpenAllPointers(columnName) {
const data = this.tableData();
const pointers = data
.map(row => row[columnName])
.filter(value => value && value.__type === 'Pointer' && value.className && value.objectId);

// Open each unique pointer in a new tab
const uniquePointers = new Map();
pointers.forEach(pointer => {
// Use a more collision-proof key format with explicit separators
const key = `className:${pointer.className}|objectId:${pointer.objectId}`;
if (!uniquePointers.has(key)) {
uniquePointers.set(key, pointer);
}
});

if (uniquePointers.size === 0) {
this.showNote('No pointers found in this column', true);
return;
}

const pointersArray = Array.from(uniquePointers.values());

// Confirm for large numbers of tabs to prevent overwhelming the user
if (pointersArray.length > 10) {
const confirmMessage = `This will open ${pointersArray.length} new tabs. This might overwhelm your browser. Continue?`;
if (!confirm(confirmMessage)) {
return;
}
}

// Open all tabs immediately to maintain user activation context
let errorCount = 0;

pointersArray.forEach((pointer) => {
try {
const filters = JSON.stringify([{ field: 'objectId', constraint: 'eq', compareTo: pointer.objectId }]);
const url = generatePath(
this.context,
`browser/${pointer.className}?filters=${encodeURIComponent(filters)}`,
true
);
window.open(url, '_blank', 'noopener,noreferrer');
// Note: window.open with security attributes may return null even when successful,
// so we assume success unless an exception is thrown
} catch (error) {
console.error('Failed to open tab for pointer:', pointer, error);
errorCount++;
}
});

// Show result notification
if (errorCount === 0) {
this.showNote(`Opened ${pointersArray.length} pointer${pointersArray.length > 1 ? 's' : ''} in new tab${pointersArray.length > 1 ? 's' : ''}`, false);
} else if (errorCount < pointersArray.length) {
this.showNote(`Opened ${pointersArray.length - errorCount} of ${pointersArray.length} tabs. ${errorCount} failed to open.`, true);
} else {
this.showNote('Unable to open tabs. Please allow popups for this site and try again.', true);
}
}
Comment on lines +867 to +926
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Treat popup-blocker null returns as failures; guard on empty/non-array data.
Today you only count exceptions; most blockers return null without throwing, leading to false “Opened N…” notes.

   handleOpenAllPointers(columnName) {
-    const data = this.tableData();
+    const data = this.tableData();
+    if (!Array.isArray(data) || data.length === 0) {
+      this.showNote('No data loaded', true);
+      return;
+    }
@@
-    pointersArray.forEach((pointer) => {
+    pointersArray.forEach((pointer) => {
       try {
         const filters = JSON.stringify([{ field: 'objectId', constraint: 'eq', compareTo: pointer.objectId }]);
         const url = generatePath(
           this.context,
           `browser/${pointer.className}?filters=${encodeURIComponent(filters)}`,
           true
         );
-        window.open(url, '_blank', 'noopener,noreferrer');
-        // Note: window.open with security attributes may return null even when successful,
-        // so we assume success unless an exception is thrown
+        const win = window.open(url, '_blank', 'noopener,noreferrer');
+        if (!win) {
+          // Likely blocked by a popup blocker
+          errorCount++;
+        }
       } catch (error) {
         console.error('Failed to open tab for pointer:', pointer, error);
         errorCount++;
       }
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
handleOpenAllPointers(columnName) {
const data = this.tableData();
const pointers = data
.map(row => row[columnName])
.filter(value => value && value.__type === 'Pointer' && value.className && value.objectId);
// Open each unique pointer in a new tab
const uniquePointers = new Map();
pointers.forEach(pointer => {
// Use a more collision-proof key format with explicit separators
const key = `className:${pointer.className}|objectId:${pointer.objectId}`;
if (!uniquePointers.has(key)) {
uniquePointers.set(key, pointer);
}
});
if (uniquePointers.size === 0) {
this.showNote('No pointers found in this column', true);
return;
}
const pointersArray = Array.from(uniquePointers.values());
// Confirm for large numbers of tabs to prevent overwhelming the user
if (pointersArray.length > 10) {
const confirmMessage = `This will open ${pointersArray.length} new tabs. This might overwhelm your browser. Continue?`;
if (!confirm(confirmMessage)) {
return;
}
}
// Open all tabs immediately to maintain user activation context
let errorCount = 0;
pointersArray.forEach((pointer) => {
try {
const filters = JSON.stringify([{ field: 'objectId', constraint: 'eq', compareTo: pointer.objectId }]);
const url = generatePath(
this.context,
`browser/${pointer.className}?filters=${encodeURIComponent(filters)}`,
true
);
window.open(url, '_blank', 'noopener,noreferrer');
// Note: window.open with security attributes may return null even when successful,
// so we assume success unless an exception is thrown
} catch (error) {
console.error('Failed to open tab for pointer:', pointer, error);
errorCount++;
}
});
// Show result notification
if (errorCount === 0) {
this.showNote(`Opened ${pointersArray.length} pointer${pointersArray.length > 1 ? 's' : ''} in new tab${pointersArray.length > 1 ? 's' : ''}`, false);
} else if (errorCount < pointersArray.length) {
this.showNote(`Opened ${pointersArray.length - errorCount} of ${pointersArray.length} tabs. ${errorCount} failed to open.`, true);
} else {
this.showNote('Unable to open tabs. Please allow popups for this site and try again.', true);
}
}
handleOpenAllPointers(columnName) {
const data = this.tableData();
if (!Array.isArray(data) || data.length === 0) {
this.showNote('No data loaded', true);
return;
}
const pointers = data
.map(row => row[columnName])
.filter(value => value && value.__type === 'Pointer' && value.className && value.objectId);
// Open each unique pointer in a new tab
const uniquePointers = new Map();
pointers.forEach(pointer => {
// Use a more collision-proof key format with explicit separators
const key = `className:${pointer.className}|objectId:${pointer.objectId}`;
if (!uniquePointers.has(key)) {
uniquePointers.set(key, pointer);
}
});
if (uniquePointers.size === 0) {
this.showNote('No pointers found in this column', true);
return;
}
const pointersArray = Array.from(uniquePointers.values());
// Confirm for large numbers of tabs to prevent overwhelming the user
if (pointersArray.length > 10) {
const confirmMessage = `This will open ${pointersArray.length} new tabs. This might overwhelm your browser. Continue?`;
if (!confirm(confirmMessage)) {
return;
}
}
// Open all tabs immediately to maintain user activation context
let errorCount = 0;
pointersArray.forEach((pointer) => {
try {
const filters = JSON.stringify([{ field: 'objectId', constraint: 'eq', compareTo: pointer.objectId }]);
const url = generatePath(
this.context,
`browser/${pointer.className}?filters=${encodeURIComponent(filters)}`,
true
);
- window.open(url, '_blank', 'noopener,noreferrer');
- // Note: window.open with security attributes may return null even when successful,
const win = window.open(url, '_blank', 'noopener,noreferrer');
if (!win) {
// Likely blocked by a popup blocker
errorCount++;
}
} catch (error) {
console.error('Failed to open tab for pointer:', pointer, error);
errorCount++;
}
});
// Show result notification
if (errorCount === 0) {
this.showNote(`Opened ${pointersArray.length} pointer${pointersArray.length > 1 ? 's' : ''} in new tab${pointersArray.length > 1 ? 's' : ''}`, false);
} else if (errorCount < pointersArray.length) {
this.showNote(`Opened ${pointersArray.length - errorCount} of ${pointersArray.length} tabs. ${errorCount} failed to open.`, true);
} else {
this.showNote('Unable to open tabs. Please allow popups for this site and try again.', true);
}
}


showNote(message, isError) {
if (!message) {
return;
Expand Down
56 changes: 56 additions & 0 deletions src/dashboard/Data/Views/Views.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,62 @@
text-overflow: ellipsis;
}

.headerText {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
min-width: 0; // Enable text truncation in flex containers
}

.headerLabel {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0; // Enable text truncation
}

.pointerIcon {
// Reset button styles
border: none;
padding: 0;
font: inherit;
color: inherit;
background: rgba(255, 255, 255, 0.2);

// Custom styles
cursor: pointer;
opacity: 0.7;
transition: opacity 0.2s ease;
z-index: 10;
pointer-events: auto;
display: inline-flex;
align-items: center;
justify-content: center;
height: 20px;
width: 20px;
border-radius: 50%;
margin-left: 5px;
flex-shrink: 0;

& svg {
transform: rotate(316deg);
}

&:hover {
opacity: 1;
background: rgba(255, 255, 255, 0.3);
}

&:focus {
opacity: 1;
background: rgba(255, 255, 255, 0.4);
outline: 2px solid rgba(255, 255, 255, 0.8);
outline-offset: 1px;
}
}

.handle {
position: absolute;
top: 0;
Expand Down
Loading