-
-
Notifications
You must be signed in to change notification settings - Fork 40
Feature/handle non renderable content 1067 #1095
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
761dc0a
029ebc1
f87ffe1
6b9f324
1172f8e
78b90c7
8f5839d
3b7bf50
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# File Preview Feature Implementation | ||
|
||
## Summary | ||
|
||
This implementation addresses GitHub issue #1067 by adding better handling for non-renderable content in the web monitoring UI. Instead of trying to display raw content that may cause download prompts or blank spaces, the system now shows informative file previews for content that cannot be rendered inline. | ||
|
||
## Changes Made | ||
|
||
### New Components | ||
|
||
1. **`FilePreview` Component** (`src/components/file-preview.jsx`) | ||
- Displays file information including name, media type, size, hash, and capture time | ||
- Provides "View Raw File" and "Download File" buttons | ||
- Shows clear messaging about why the file cannot be rendered inline | ||
|
||
2. **`SideBySideFilePreview` Component** (`src/components/side-by-side-file-preview.jsx`) | ||
- Displays two file previews side-by-side for version comparisons | ||
- Consistent with existing side-by-side layout patterns | ||
|
||
3. **File Preview Styles** (`src/components/file-preview.css`) | ||
- Clean, responsive styling for file preview components | ||
- Warning messages and action buttons properly styled | ||
- Mobile-friendly responsive design | ||
|
||
### Enhanced Diff Types | ||
|
||
4. **New Diff Types** (in `src/constants/diff-types.js`) | ||
- `FILE_PREVIEW`: Single file preview | ||
- `SIDE_BY_SIDE_FILE_PREVIEW`: Side-by-side file preview | ||
- Updated `diffTypesFor()` function to automatically detect non-renderable content | ||
|
||
5. **Smart Content Detection** | ||
- Added `isNonRenderableType()` function to identify content that should use file preview | ||
- Maintains existing behavior for renderable content (HTML, text, images, PDFs, etc.) | ||
- Defaults to file preview for Office documents, archives, and other binary formats | ||
|
||
### Integration Updates | ||
|
||
6. **`DiffView` Component Updates** (`src/components/diff-view.jsx`) | ||
- Added support for rendering new file preview diff types | ||
- Imports and handles new components | ||
|
||
7. **`ChangeView` Component Updates** (`src/components/change-view/change-view.jsx`) | ||
- Smart default diff type selection based on content type | ||
- Non-renderable content defaults to `SIDE_BY_SIDE_FILE_PREVIEW` | ||
- Renderable content maintains existing defaults | ||
|
||
## File Types Handled | ||
|
||
### Non-Renderable (uses File Preview): | ||
- Microsoft Office documents (.xlsx, .docx, .ppt, etc.) | ||
- Archive files (.zip, .tar.gz, etc.) | ||
- Binary executables and data files | ||
- Specialized formats without browser support | ||
|
||
### Renderable (uses existing views): | ||
- HTML content | ||
- Plain text and code files | ||
- PDFs (browser-native support) | ||
- Images (PNG, JPEG, GIF, SVG, etc.) | ||
- Audio/video files | ||
- JSON and XML | ||
|
||
## User Experience | ||
|
||
- When viewing non-renderable content, users see a clean information card instead of download prompts | ||
- Clear file metadata including type, size, and hash | ||
- Easy access to raw file content via "View Raw File" button | ||
- Download option with proper filename | ||
- Consistent side-by-side layout for comparisons | ||
- Informative messaging explaining why inline rendering isn't available | ||
|
||
## Screenshot | ||
|
||
 | ||
*New file preview interface showing non-renderable content with metadata instead of triggering browser downloads* | ||
|
||
## Testing | ||
|
||
- Added comprehensive test coverage for new components | ||
- Tests verify proper file information display | ||
- Tests confirm appropriate diff type selection based on content type | ||
- All existing tests continue to pass | ||
|
||
## Backward Compatibility | ||
|
||
- Existing functionality for renderable content is unchanged | ||
- Raw view options remain available as fallback | ||
- No breaking changes to existing APIs or user workflows | ||
|
||
## Future Enhancements | ||
|
||
This foundation could be extended to support: | ||
- Specialized viewers for specific file types | ||
- File size information from server metadata | ||
- Thumbnail generation for supported formats | ||
- More detailed file analysis and comparison |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this file here for? It is not used. (It also doesn’t actually look like what File Preview component renders.) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* eslint-env jest */ | ||
|
||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
|
||
import { render } from '@testing-library/react'; | ||
import FilePreview from '../file-preview'; | ||
|
||
describe('FilePreview', () => { | ||
const mockPage = { | ||
url: 'https://example.com/test.xlsx', | ||
uuid: 'mock-page-uuid' | ||
}; | ||
|
||
const mockVersion = { | ||
uuid: 'mock-version-uuid', | ||
media_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | ||
body_url: 'https://example.com/versions/test.xlsx', | ||
body_hash: 'abc123def456', | ||
capture_time: '2023-01-01T12:00:00Z' | ||
}; | ||
|
||
it('renders file information correctly', () => { | ||
const { getByText } = render( | ||
<FilePreview page={mockPage} version={mockVersion} /> | ||
); | ||
|
||
expect(getByText('File Information')).toBeInTheDocument(); | ||
expect(getByText('test.xlsx')).toBeInTheDocument(); | ||
expect(getByText('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')).toBeInTheDocument(); | ||
expect(getByText('abc123def456')).toBeInTheDocument(); | ||
}); | ||
|
||
it('renders download and view buttons', () => { | ||
const { getByText } = render( | ||
<FilePreview page={mockPage} version={mockVersion} /> | ||
); | ||
|
||
const viewButton = getByText('View Raw File'); | ||
const downloadButton = getByText('Download File'); | ||
|
||
expect(viewButton).toBeInTheDocument(); | ||
expect(viewButton.getAttribute('href')).toBe('https://example.com/versions/test.xlsx'); | ||
expect(downloadButton).toBeInTheDocument(); | ||
expect(downloadButton.getAttribute('href')).toBe('https://example.com/versions/test.xlsx'); | ||
}); | ||
|
||
it('shows non-renderable content warning', () => { | ||
const { getByText } = render( | ||
<FilePreview page={mockPage} version={mockVersion} /> | ||
); | ||
|
||
expect(getByText(/This file type cannot be rendered inline/)).toBeInTheDocument(); | ||
}); | ||
|
||
it('handles missing filename gracefully', () => { | ||
const versionWithoutFilename = { | ||
...mockVersion, | ||
body_url: 'https://example.com/versions/' | ||
}; | ||
const pageWithoutFilename = { | ||
...mockPage, | ||
url: 'https://example.com/' | ||
}; | ||
|
||
const { getByText } = render( | ||
<FilePreview page={pageWithoutFilename} version={versionWithoutFilename} /> | ||
); | ||
|
||
expect(getByText('Unknown')).toBeInTheDocument(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* eslint-env jest */ | ||
|
||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
|
||
import { render } from '@testing-library/react'; | ||
import SideBySideFilePreview from '../side-by-side-file-preview'; | ||
|
||
// Mock the FilePreview component since we test it separately | ||
jest.mock('../file-preview', () => { | ||
return function MockFilePreview ({ page, version, content }) { | ||
return <div data-testid="file-preview">{version.uuid}</div>; | ||
}; | ||
}); | ||
|
||
describe('SideBySideFilePreview', () => { | ||
const mockPage = { | ||
url: 'https://example.com/test.xlsx', | ||
uuid: 'mock-page-uuid' | ||
}; | ||
|
||
const mockVersionA = { | ||
uuid: 'version-a-uuid', | ||
media_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | ||
body_url: 'https://example.com/versions/test-a.xlsx', | ||
}; | ||
|
||
const mockVersionB = { | ||
uuid: 'version-b-uuid', | ||
media_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | ||
body_url: 'https://example.com/versions/test-b.xlsx', | ||
}; | ||
|
||
const mockDiffData = { | ||
rawA: 'raw content A', | ||
rawB: 'raw content B' | ||
}; | ||
|
||
it('renders both versions side by side', () => { | ||
const { getByText, getAllByTestId } = render( | ||
<SideBySideFilePreview | ||
page={mockPage} | ||
a={mockVersionA} | ||
b={mockVersionB} | ||
diffData={mockDiffData} | ||
/> | ||
); | ||
|
||
expect(getByText('From Version')).toBeInTheDocument(); | ||
expect(getByText('To Version')).toBeInTheDocument(); | ||
|
||
const filePreviews = getAllByTestId('file-preview'); | ||
expect(filePreviews).toHaveLength(2); | ||
expect(filePreviews[0]).toHaveTextContent('version-a-uuid'); | ||
expect(filePreviews[1]).toHaveTextContent('version-b-uuid'); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ import baseStyles from '../../css/base.css'; | |
import viewStyles from './change-view.css'; | ||
|
||
export const defaultDiffType = 'SIDE_BY_SIDE_RENDERED'; | ||
export const defaultFilePreviewDiffType = 'SIDE_BY_SIDE_FILE_PREVIEW'; | ||
export const diffTypeStorage = 'edgi.wm.ui.diff_type'; | ||
|
||
const collapsedViewStorage = 'WebMonitoring.ChangeView.collapsedView'; | ||
|
@@ -431,7 +432,14 @@ function saveDiffSettings (settings) { | |
|
||
function ensureValidDiffType (from, to, page, stateDiffType = null) { | ||
const relevantTypes = relevantDiffTypes(from, to, page); | ||
const typesToTry = ([stateDiffType, loadDiffType(), defaultDiffType]).filter(t => t); | ||
|
||
// Determine the appropriate default based on content type | ||
const isNonRenderable = relevantTypes.some(type => | ||
type.value === 'FILE_PREVIEW' || type.value === 'SIDE_BY_SIDE_FILE_PREVIEW' | ||
); | ||
const appropriateDefault = isNonRenderable ? defaultFilePreviewDiffType : defaultDiffType; | ||
|
||
const typesToTry = ([stateDiffType, loadDiffType(), appropriateDefault]).filter(t => t); | ||
Comment on lines
-434
to
+442
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels a little overcomplicated. The goal of this new “file preview” view is to have something to show if we don’t believe we have a diff that can render something appropriate, so I think it would make a little more sense to just replace |
||
|
||
return typesToTry.find(diffType => isDiffTypeRelevant(relevantTypes, diffType)) | ||
|| relevantTypes[0].value; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ import SideBySideRenderedDiff from './side-by-side-rendered-diff'; | |
import ChangesOnlyDiff from './changes-only-diff'; | ||
import RawVersion from './raw-version'; | ||
import SideBySideRawVersions from './side-by-side-raw-versions'; | ||
import FilePreview from './file-preview'; | ||
import SideBySideFilePreview from './side-by-side-file-preview'; | ||
|
||
import styles from '../css/base.css'; | ||
|
||
|
@@ -186,6 +188,14 @@ export default class DiffView extends Component { | |
return ( | ||
<ChangesOnlyDiff diffData={this.state.diffData} className='diff-source-inline' /> | ||
); | ||
case diffTypes.FILE_PREVIEW.value: | ||
return ( | ||
<FilePreview page={this.props.page} version={this.props.b} content={this.state.diffData.rawB} /> | ||
); | ||
case diffTypes.SIDE_BY_SIDE_FILE_PREVIEW.value: | ||
return ( | ||
<SideBySideFilePreview {...commonProps} /> | ||
); | ||
Comment on lines
+191
to
+198
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realize I wrote this in the original issue:
…but looking at this in practice, we probably should not have two different options here (so, my bad on not thinking this through originally). This should just be one diff type labeled “file preview” that shows the previews for both versions. Since the preview is a brief summary and doesn’t show any actual content or anything, it doesn’t really make sense to give people an option to only view one version’s preview. (That said, it’s still probably nice to break this up into two React components! The component that is the diff view [ |
||
default: | ||
return null; | ||
} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A few notes:
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* File Preview Styles */ | ||
.file-preview { | ||
max-width: 100%; | ||
margin: 1rem 0; | ||
} | ||
|
||
.file-preview__info { | ||
padding: 1.5rem; | ||
margin-bottom: 1rem; | ||
border: 1px solid #ddd; | ||
border-radius: 4px; | ||
background-color: #f9f9f9; | ||
} | ||
|
||
.file-preview__title { | ||
margin-top: 0; | ||
margin-bottom: 1rem; | ||
color: #333; | ||
font-size: 1.25rem; | ||
} | ||
|
||
.file-preview__details { | ||
margin-bottom: 1.5rem; | ||
} | ||
|
||
.file-preview__detail { | ||
margin-bottom: 0.75rem; | ||
line-height: 1.4; | ||
} | ||
|
||
.file-preview__detail strong { | ||
display: inline-block; | ||
min-width: 100px; | ||
color: #555; | ||
} | ||
|
||
.file-preview__hash { | ||
font-family: 'Courier New', Courier, monospace; | ||
font-size: 0.85em; | ||
background-color: #f4f4f4; | ||
padding: 0.2em 0.4em; | ||
border-radius: 3px; | ||
word-break: break-all; | ||
margin-left: 0.5rem; | ||
} | ||
|
||
.file-preview__actions { | ||
display: flex; | ||
gap: 0.75rem; | ||
flex-wrap: wrap; | ||
} | ||
|
||
.file-preview__download { | ||
text-decoration: none; | ||
padding: 0.5rem 1rem; | ||
border-radius: 4px; | ||
font-weight: 500; | ||
transition: background-color 0.2s; | ||
} | ||
|
||
.file-preview__download:hover { | ||
text-decoration: none; | ||
} | ||
|
||
.file-preview__warning { | ||
background-color: #fff3cd; | ||
border: 1px solid #ffeaa7; | ||
border-radius: 4px; | ||
padding: 1rem; | ||
color: #856404; | ||
} | ||
|
||
.file-preview__warning p { | ||
margin: 0; | ||
line-height: 1.4; | ||
} | ||
|
||
/* Side-by-side file preview styles */ | ||
.side-by-side-file-preview { | ||
display: flex; | ||
gap: 1rem; | ||
width: 100%; | ||
} | ||
|
||
.side-by-side-file-preview__version { | ||
flex: 1; | ||
min-width: 0; /* Allow flex items to shrink below their content size */ | ||
} | ||
|
||
.side-by-side-file-preview__version-title { | ||
margin-top: 0; | ||
margin-bottom: 1rem; | ||
padding: 0.5rem 0; | ||
border-bottom: 2px solid #007cba; | ||
color: #007cba; | ||
font-size: 1.1rem; | ||
font-weight: 600; | ||
} | ||
Comment on lines
+78
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These belong to a separate component and so should be in a separate file dedicated to that component (also in a directory with that component’s JSX file, like this file should be). |
||
|
||
/* Responsive design for smaller screens */ | ||
@media (max-width: 768px) { | ||
.side-by-side-file-preview { | ||
flex-direction: column; | ||
} | ||
|
||
.file-preview__actions { | ||
flex-direction: column; | ||
} | ||
|
||
.file-preview__download { | ||
text-align: center; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this file doing here?