Commit cd458656 authored by David Sikter's avatar David Sikter
Browse files

Use urls for uploads in the store and file browser

parent ace60545
Pipeline #138279 passed with stages
in 89 minutes and 58 seconds
......@@ -19,6 +19,7 @@ import React, { useContext, useRef } from 'react'
import PropTypes from 'prop-types'
import { useApi, DoesNotExist } from './api'
import { useErrors } from './errors'
import { apiBase } from '../config'
function addSubscription(storeObj, cb, options) {
storeObj.subscriptions.push({cb, ...options})
......@@ -62,13 +63,16 @@ const DataStore = React.memo(({children}) => {
* an object with default values, mostly undefined or nulls, will be returned). Note, it
* does not cause the store to fetch any data; for that, a subscription is necessary.
*/
function getUpload(uploadId) {
function getUpload(installationUrl, uploadId) {
if (!uploadId) return undefined
if (installationUrl !== apiBase) throw new Error('Fetching uploads from external installations is not yet supported')
let uploadStoreObj = uploadStore.current[uploadId]
if (!uploadStoreObj) {
// Creates an initial, empty upload store object.
uploadStoreObj = {
uploadId: uploadId, // ReadOnly
installationUrl, // ReadOnly
uploadId, // ReadOnly
isExternal: installationUrl !== apiBase, // ReadOnly
deletionRequested: false, // Writable - If this upload has been sent for deletion
upload: undefined, // Writeable - The last upload proc data fetched.
entries: undefined, // ReadOnly - The last list of entries fetched by the store (when subscribing to an entry page).
......@@ -88,7 +92,11 @@ const DataStore = React.memo(({children}) => {
error: undefined, // If we had an api error from the last store refresh
subscriptions: [],
isRefreshing: false,
refreshOptions: null // The options used at the last store refresh
refreshOptions: null, // The options used at the last store refresh
// Convenience methods
updateUpload: (dataToUpdate) => { updateUpload(installationUrl, uploadId, dataToUpdate) },
requestRefreshUpload: () => { requestRefreshUpload(installationUrl, uploadId) }
}
uploadStore.current[uploadId] = uploadStoreObj
}
......@@ -101,9 +109,9 @@ const DataStore = React.memo(({children}) => {
* will be removed when finished. Note that to keep the object in the store and watch it
* also after this call has ended, you need to set up a subscription.
*/
async function getUploadAsync(uploadId, requireUpload, requireEntriesPage) {
async function getUploadAsync(installationUrl, uploadId, requireUpload, requireEntriesPage) {
if (!uploadId) return undefined
const uploadStoreObj = getUpload(uploadId)
const uploadStoreObj = getUpload(installationUrl, uploadId)
if (uploadRefreshSatisfiesOptions(uploadStoreObj, requireUpload, requireEntriesPage)) {
return uploadStoreObj // Store has already been refreshed with the required options
}
......@@ -115,7 +123,7 @@ const DataStore = React.memo(({children}) => {
resolve(newStoreObj)
}
}
subscribeToUpload(uploadId, cb, requireUpload, requireEntriesPage)
subscribeToUpload(installationUrl, uploadId, cb, requireUpload, requireEntriesPage)
})
}
......@@ -123,22 +131,23 @@ const DataStore = React.memo(({children}) => {
* Subscribes the callback cb to an upload, and returns a function to be called to unsubscribe.
* Typically used in useEffect. The callback will be called when the store value changes.
*/
function subscribeToUpload(uploadId, cb, requireUpload, requireEntriesPage) {
function subscribeToUpload(installationUrl, uploadId, cb, requireUpload, requireEntriesPage) {
if (!uploadId) return undefined
if (requireUpload === undefined || requireEntriesPage === undefined) {
throw Error('Store error: missing upload subscription parameter')
}
const uploadStoreObj = getUpload(uploadId)
const uploadStoreObj = getUpload(installationUrl, uploadId)
addSubscription(uploadStoreObj, cb, {requireUpload, requireEntriesPage})
initiateUploadRefreshIfNeeded(uploadId)
initiateUploadRefreshIfNeeded(installationUrl, uploadId)
return function unsubscriber() { removeSubscription(uploadStore.current, uploadId, cb) }
}
/**
* Updates the store upload with the specified data and notifies all subscribers.
*/
function updateUpload(uploadId, dataToUpdate) {
const oldStoreObj = getUpload(uploadId)
function updateUpload(installationUrl, uploadId, dataToUpdate) {
if (installationUrl !== apiBase) throw new Error('Cannot update external upload')
const oldStoreObj = getUpload(installationUrl, uploadId)
const newStoreObj = {...oldStoreObj, ...dataToUpdate}
// Compute derived values
const user = userRef.current
......@@ -174,7 +183,7 @@ const DataStore = React.memo(({children}) => {
}
}
// Possibly, start a refresh job
initiateUploadRefreshIfNeeded(uploadId)
initiateUploadRefreshIfNeeded(installationUrl, uploadId)
}
function uploadOptions(uploadStoreObj) {
......@@ -197,9 +206,9 @@ const DataStore = React.memo(({children}) => {
return false
}
async function refreshUpload(uploadId) {
async function refreshUpload(installationUrl, uploadId) {
// Internal use: refresh an upload store obj with data from the API.
const uploadStoreObj = getUpload(uploadId)
const uploadStoreObj = getUpload(installationUrl, uploadId)
const refreshOptions = uploadOptions(uploadStoreObj)
const {requireUpload, requireEntriesPage} = refreshOptions
if (!requireUpload && !requireEntriesPage) return
......@@ -215,42 +224,42 @@ const DataStore = React.memo(({children}) => {
const dataToUpdate = requireEntriesPage
? {error: undefined, isRefreshing: false, upload: apiData.response?.upload, entries: apiData.response?.data, apiData, pagination: currentPagination, refreshOptions}
: {error: undefined, isRefreshing: false, upload: apiData.data, entries: undefined, apiData: undefined, refreshOptions}
updateUpload(uploadId, dataToUpdate)
updateUpload(installationUrl, uploadId, dataToUpdate)
}).catch((error) => {
if (requireEntriesPage && error.apiMessage === 'Page out of range requested.') {
// Special case: can happen if entries have been deleted and the page we were on is no longer in range
if (currentPagination && currentPagination.page !== 1) {
// Rather than sending an update to all subscribers with an error, we first try
// jumping to page 1 (will probably solve the problem)
getUpload(uploadId).pagination.page = 1
refreshUpload(uploadId)
getUpload(installationUrl, uploadId).pagination.page = 1
refreshUpload(installationUrl, uploadId)
return
}
}
updateUpload(uploadId, {error: error, isRefreshing: false, refreshOptions})
updateUpload(installationUrl, uploadId, {error: error, isRefreshing: false, refreshOptions})
})
}
/**
* Use to nicely request a refresh of the upload store object.
*/
function requestRefreshUpload(uploadId) {
const uploadStoreObj = getUpload(uploadId)
function requestRefreshUpload(installationUrl, uploadId) {
const uploadStoreObj = getUpload(installationUrl, uploadId)
if (!uploadStoreObj.isRefreshing) {
// Refresh is not already in progress
refreshUpload(uploadId)
refreshUpload(installationUrl, uploadId)
}
}
async function initiateUploadRefreshIfNeeded(uploadId) {
async function initiateUploadRefreshIfNeeded(installationUrl, uploadId) {
// Internal use: check if a refresh of the store is needed, and if so, initiate it.
let uploadStoreObj = getUpload(uploadId)
let uploadStoreObj = getUpload(installationUrl, uploadId)
if (uploadStoreObj.isRefreshing) return // refresh already in progress
if (uploadStoreObj.isProcessing) {
// Upload is processing
uploadStoreObj.isRefreshing = true // Signal start of a refresh
await new Promise(resolve => setTimeout(resolve, 1000)) // wait one sec
uploadStoreObj = getUpload(uploadId)
uploadStoreObj = getUpload(installationUrl, uploadId)
}
// Determine if a refresh is needed or not
const {requireUpload, requireEntriesPage} = uploadOptions(uploadStoreObj)
......@@ -261,7 +270,7 @@ const DataStore = React.memo(({children}) => {
const wrongPagination = requireEntriesPage && (pagIs?.page !== pag?.page || pagIs?.page_size !== pag.page_size)
if (!uploadStoreObj.error && (uploadDataMissing || entryDataMissing || wrongPagination || uploadStoreObj.isProcessing)) {
// Need to fetch data from the api
refreshUpload(uploadId)
refreshUpload(installationUrl, uploadId)
} else {
uploadStoreObj.isRefreshing = false
}
......
......@@ -53,7 +53,7 @@ import SaveIcon from '@material-ui/icons/Save'
import AddIcon from '@material-ui/icons/AddCircle'
import CodeIcon from '@material-ui/icons/Code'
import DeleteIcon from '@material-ui/icons/Delete'
import {getLineStyles, titleCase} from '../../utils'
import {getLineStyles, titleCase, createUploadUrl} from '../../utils'
import Plot from '../visualization/Plot'
import { useUploadPageContext } from '../uploads/UploadPageContext'
import {EntryButton} from '../nav/Routes'
......@@ -63,6 +63,7 @@ import Alert from '@material-ui/lab/Alert'
import _ from 'lodash'
import ReloadIcon from '@material-ui/icons/Replay'
import UploadIcon from '@material-ui/icons/CloudUpload'
import { apiBase } from '../../config'
export function useBrowserAdaptorContext(data) {
const entryPageContext = useEntryPageContext()
......@@ -530,8 +531,9 @@ class SectionAdaptor extends ArchiveAdaptor {
if (property.m_annotations.browser[0].adaptor === 'RawFileAdaptor') {
const uploadId = this.context.archive.metadata.upload_id
const path = this.obj[property.name]
const uploadUrl = createUploadUrl(apiBase, uploadId, path) // TODO: installationUrl should be fetched from adaptor when archive adaptors refactored to use urls
const response = await this.context.api.get(`uploads/${uploadId}/rawdir/${path}`)
return new RawFileAdaptor(this.context, uploadId, path, response.file_metadata, false)
return new RawFileAdaptor(this.context, uploadUrl, response.file_metadata, false)
}
}
return this.adaptorFactory(value, property, this.obj)
......
......@@ -82,7 +82,7 @@ export class Adaptor {
* Renders the contents of the current lane (the lane that this adaptor represents).
*/
render() {
return ''
return null
}
}
......@@ -253,7 +253,7 @@ export const Browser = React.memo(function Browser({adaptor, form}) {
if (url === undefined) {
// Can happen when navigating to another tab, possibly with the browser's back/forward buttons
// We want to keep the cached lanes, in case the user goes back to this tab, so return immediately.
return
return null
}
return <browserContext.Provider value={contextValue}>
......@@ -317,7 +317,7 @@ function Lane({lane}) {
</div>
}
if (!adaptor || !initialized) {
return ''
return null
}
return <div className={classes.root} key={`lane:${lane.path}`} data-testid={`lane${lane.index}:${lane.key}`}>
<div className={classes.container} ref={containerRef}>
......@@ -401,7 +401,7 @@ export const ItemLink = React.forwardRef(function ItemLink({itemKey, ...props},
/>
} else {
// If this is used in a non Browser context
return ''
return null
}
})
ItemLink.propTypes = {
......@@ -503,7 +503,7 @@ export function Content(props) {
export function Compartment({title, children, color}) {
if (!React.Children.count(children)) {
return ''
return null
}
return <React.Fragment>
<Box paddingTop={1} whiteSpace="nowrap">
......
......@@ -43,49 +43,56 @@ import NorthLaunchButton from '../north/NorthLaunchButton'
import { useTools } from '../north/NorthPage'
import { EntryButton } from '../nav/Routes'
import { useErrors } from '../errors'
import { apiBase } from '../../config'
import { parseNomadUrl, refType, urlJoin, urlEncodePath } from '../../utils'
const FileBrowser = React.memo(({uploadId, path, rootTitle, highlightedItem = null}) => {
const FileBrowser = React.memo(({uploadUrl, rootTitle, highlightedItem = null}) => {
const context = useBrowserAdaptorContext()
const adaptor = useMemo(() => {
return new RawDirectoryAdaptor(
context, uploadId, path, rootTitle, highlightedItem)
}, [context, uploadId, path, rootTitle, highlightedItem])
context, uploadUrl, rootTitle, highlightedItem)
}, [context, uploadUrl, rootTitle, highlightedItem])
if (!context.metainfo) {
return ''
return null
}
return <Browser adaptor={adaptor} />
})
FileBrowser.propTypes = {
uploadId: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
uploadUrl: PropTypes.string.isRequired,
rootTitle: PropTypes.string.isRequired,
highlightedItem: PropTypes.string
}
export default FileBrowser
class RawDirectoryAdaptor extends Adaptor {
constructor(context, uploadId, path, title, highlightedItem) {
constructor(context, uploadUrl, title, highlightedItem) {
super(context)
this.uploadId = uploadId
this.path = path
const parsedUrl = parseNomadUrl(uploadUrl)
if (parsedUrl.type !== refType.upload) throw new Error(`Expected an upload url, got ${uploadUrl}`)
if (!parsedUrl.isResolved) throw new Error(`Absolute url required, got ${uploadUrl}`)
this.installationUrl = parsedUrl.installationUrl
this.uploadUrl = uploadUrl
this.uploadId = parsedUrl.uploadId
this.path = parsedUrl.path
this.title = title
this.highlightedItem = highlightedItem
this.editable = undefined
this.data = undefined
this.timestamp = undefined
this.initialized = false
this.dependencies = new Set([uploadId])
this.dependencies = new Set([this.uploadId])
}
depends() {
return this.dependencies
}
async initialize(api, dataStore) {
const uploadStoreObj = await dataStore.getUploadAsync(this.uploadId, true, false)
const uploadStoreObj = await dataStore.getUploadAsync(this.installationUrl, this.uploadId, true, false)
this.timestamp = uploadStoreObj.upload?.complete_time
this.editable = uploadStoreObj.isEditable
const encodedPath = this.path.split('/').map(segment => encodeURIComponent(segment)).join('/')
const encodedPath = urlEncodePath(this.path)
if (this.installationUrl !== apiBase) throw new Error('Fetching directory data from external source is not yet supported')
const response = await api.get(`/uploads/${this.uploadId}/rawdir/${encodedPath}?include_entry_info=true&page_size=500`)
const elementsByName = {}
response.directory_metadata.content.forEach(element => { elementsByName[element.name] = element })
......@@ -95,20 +102,21 @@ class RawDirectoryAdaptor extends Adaptor {
if (key === '_mainfile') {
key = this.highlightedItem
}
const ext_path = this.path ? this.path + '/' + key : key
const extendedUrl = urlJoin(this.uploadUrl, encodeURIComponent(key))
const element = this.data.elementsByName[key]
if (element) {
if (element.is_file) {
return new RawFileAdaptor(this.context, this.uploadId, ext_path, element, this.editable)
return new RawFileAdaptor(this.context, extendedUrl, element, this.editable)
} else {
return new RawDirectoryAdaptor(this.context, this.uploadId, ext_path, key, null)
return new RawDirectoryAdaptor(this.context, extendedUrl, key, null)
}
}
throw new Error('Bad path: ' + key)
}
render() {
return <RawDirectoryContent
uploadId={this.uploadId} path={this.path} title={this.title} highlightedItem={this.highlightedItem}
installationUrl={this.installationUrl} uploadId={this.uploadId} path={this.path}
title={this.title} highlightedItem={this.highlightedItem}
editable={this.editable}/>
}
}
......@@ -127,13 +135,13 @@ const useRawDirectoryContentStyles = makeStyles(theme => ({
backgroundColor: theme.palette.grey[300]
}
}))
function RawDirectoryContent({uploadId, path, title, highlightedItem, editable}) {
function RawDirectoryContent({installationUrl, uploadId, path, title, highlightedItem, editable}) {
const classes = useRawDirectoryContentStyles()
const dataStore = useDataStore()
const browser = useContext(browserContext)
const lane = useContext(laneContext)
const history = useHistory()
const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/')
const encodedPath = urlEncodePath(path)
const { api } = useApi()
const [openConfirmDeleteDirDialog, setOpenConfirmDeleteDirDialog] = useState(false)
const [openCreateDirDialog, setOpenCreateDirDialog] = useState(false)
......@@ -147,7 +155,7 @@ function RawDirectoryContent({uploadId, path, title, highlightedItem, editable})
const oldTimestamp = lane.adaptor.timestamp // Timestamp from the store the last time we called initialize
const newTimestamp = newStoreObj.upload?.complete_time // Current timestamp from the store
if (!lane.adaptor.initialized) {
// No need to a new refresh again, just update the adaptor timestamp
// No need to initiate a new refresh, just update the adaptor timestamp
lane.adaptor.timestamp = newTimestamp
lane.adaptor.initialized = true
} else if (newTimestamp !== oldTimestamp && !newStoreObj.isProcessing) {
......@@ -158,9 +166,9 @@ function RawDirectoryContent({uploadId, path, title, highlightedItem, editable})
}, [browser, lane, uploadId])
useEffect(() => {
refreshIfNeeded(undefined, dataStore.getUpload(uploadId))
return dataStore.subscribeToUpload(uploadId, refreshIfNeeded, true, false)
}, [dataStore, uploadId, refreshIfNeeded])
refreshIfNeeded(undefined, dataStore.getUpload(installationUrl, uploadId))
return dataStore.subscribeToUpload(installationUrl, uploadId, refreshIfNeeded, true, false)
}, [dataStore, installationUrl, uploadId, refreshIfNeeded])
const handleDrop = (files) => {
if (!files[0]?.name) {
......@@ -171,7 +179,7 @@ function RawDirectoryContent({uploadId, path, title, highlightedItem, editable})
formData.append('file', file)
}
api.put(`/uploads/${uploadId}/raw/${encodedPath}`, formData)
.then(response => dataStore.updateUpload(uploadId, {upload: response.data}))
.then(response => dataStore.updateUpload(installationUrl, uploadId, {upload: response.data}))
.catch(error => raiseError(error))
}
......@@ -179,9 +187,9 @@ function RawDirectoryContent({uploadId, path, title, highlightedItem, editable})
setOpenCreateDirDialog(false)
const dirName = createDirName.current.value
if (dirName) {
const fullPath = encodedPath + (encodedPath ? '/' : '') + encodeURIComponent(dirName)
const fullPath = urlJoin(encodedPath, encodeURIComponent(dirName))
api.post(`/uploads/${uploadId}/raw-create-dir/${fullPath}`)
.then(response => dataStore.updateUpload(uploadId, {upload: response.data}))
.then(response => dataStore.updateUpload(installationUrl, uploadId, {upload: response.data}))
.catch(raiseError)
}
}
......@@ -191,14 +199,14 @@ function RawDirectoryContent({uploadId, path, title, highlightedItem, editable})
api.delete(`/uploads/${uploadId}/raw/${encodedPath}`)
.then(response => {
const mainfile = lane.adaptor.context?.mainfile // Will be set if we're on an entry page
if (typeof mainfile === 'string' && (mainfile === path || path === '' || mainfile.startsWith(path + '/'))) {
if (typeof mainfile === 'string' && (path === '' || mainfile === path || mainfile.startsWith(path + '/'))) {
// This will delete the current entry - go to upload overview page
history.push(`/user/uploads/upload/id/${uploadId}`)
} else {
const gotoLane = lane.index > 0 ? lane.index - 1 : 0
history.push(browser.lanes.current[gotoLane].path)
}
dataStore.updateUpload(uploadId, {upload: response.data})
dataStore.updateUpload(installationUrl, uploadId, {upload: response.data})
})
.catch(raiseError)
}
......@@ -207,7 +215,7 @@ function RawDirectoryContent({uploadId, path, title, highlightedItem, editable})
return <Content key={path}><Typography>loading ...</Typography></Content>
} else {
// Data loaded
const downloadUrl = `uploads/${uploadId}/raw/${encodedPath}?compress=true`
const downloadUrl = `uploads/${uploadId}/raw/${encodedPath}?compress=true` // TODO: installationUrl need to be considered for external uploads
return (
<Dropzone
disabled={!editable}
......@@ -313,7 +321,7 @@ function RawDirectoryContent({uploadId, path, title, highlightedItem, editable})
lane.adaptor.data.response.directory_metadata.content.map(element => (
<Item
icon={element.is_file ? (element.parser_name ? RecognizedFileIcon : FileIcon) : FolderIcon}
itemKey={element.name} key={path ? path + '/' + element.name : element.name}
itemKey={element.name} key={urlJoin(path, element.name)}
highlighted={element.name === highlightedItem}
chip={element.parser_name && element.parser_name.replace('parsers/', '').replace('archive', 'nomad')}
>
......@@ -331,6 +339,7 @@ function RawDirectoryContent({uploadId, path, title, highlightedItem, editable})
}
}
RawDirectoryContent.propTypes = {
installationUrl: PropTypes.string.isRequired,
uploadId: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
......@@ -339,13 +348,17 @@ RawDirectoryContent.propTypes = {
}
export class RawFileAdaptor extends Adaptor {
constructor(context, uploadId, path, data, editable) {
constructor(context, uploadUrl, data, editable) {
super(context)
this.uploadId = uploadId
this.path = path
const parsedUrl = parseNomadUrl(uploadUrl)
if (parsedUrl.type !== refType.upload) throw new Error(`Expected an upload url, got ${uploadUrl}`)
if (!parsedUrl.isResolved) throw new Error(`Absolute url required, got ${uploadUrl}`)
this.installationUrl = parsedUrl.installationUrl
this.uploadId = parsedUrl.uploadId
this.path = parsedUrl.path
this.data = data
this.editable = editable
this.dependencies = new Set([uploadId])
this.dependencies = new Set([this.uploadId])
}
depends() {
return this.dependencies
......@@ -369,8 +382,8 @@ export class RawFileAdaptor extends Adaptor {
}
render() {
return <RawFileContent
uploadId={this.uploadId} path={this.path} data={this.data} editable={this.editable}
key={this.path}/>
installationUrl={this.installationUrl} uploadId={this.uploadId} path={this.path}
data={this.data} editable={this.editable} key={this.path}/>
}
}
......@@ -388,7 +401,7 @@ class FilePreviewAdaptor extends Adaptor {
}
}
function RawFileContent({uploadId, path, data, editable}) {
function RawFileContent({installationUrl, uploadId, path, data, editable}) {
const browser = useContext(browserContext)
const lane = useContext(laneContext)
const history = useHistory()
......@@ -396,8 +409,8 @@ function RawFileContent({uploadId, path, data, editable}) {
const { api } = useApi()
const { raiseError } = useErrors()
const [openConfirmDeleteFileDialog, setOpenConfirmDeleteFileDialog] = useState(false)
const encodedPath = path.split('/').map(segment => encodeURIComponent(segment)).join('/')
const downloadUrl = `uploads/${uploadId}/raw/${encodedPath}?ignore_mime_type=true`
const encodedPath = urlEncodePath(path)
const downloadUrl = `uploads/${uploadId}/raw/${encodedPath}?ignore_mime_type=true` // TODO: installationUrl need to be considered for external uploads
const allNorthTools = useTools()
const applicableNorthTools = useMemo(() => {
const fileExtension = path.split('.').pop().toLowerCase()
......@@ -420,7 +433,7 @@ function RawFileContent({uploadId, path, data, editable}) {
const gotoLane = lane.index > 0 ? lane.index - 1 : 0
history.push(browser.lanes.current[gotoLane].path)
}
dataStore.updateUpload(uploadId, {upload: response.data})
dataStore.updateUpload(installationUrl, uploadId, {upload: response.data})
})
.catch(raiseError)
}
......@@ -526,6 +539,7 @@ function RawFileContent({uploadId, path, data, editable}) {
</Content>)
}
RawFileContent.propTypes = {
installationUrl: PropTypes.string.isRequired,
uploadId: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
......
......@@ -23,6 +23,8 @@ import { render, screen, within, startAPI, closeAPI } from '../conftest.spec'
import FileBrowser from './FileBrowser'
import { purgeTreePath, getLane, checkLanes, navigateTo, checkDirectoryLane, checkFileLane } from './conftest.spec'
import { minutes } from '../../setupTests'
import { createUploadUrl } from '../../utils'
import { apiBase } from '../../config'
const dirSpecialChars = 'dir special chars ~!?*\\()[]{}<>,.;:\'"`&@#$%=|'
const fileBrowserTree = {
......@@ -62,7 +64,7 @@ async function testBrowseAround(editable) {
browserTree: fileBrowserTree,
editable
}
render(<FileBrowser uploadId="browser_test" path="" rootTitle={browserConfig.rootTitle}/>)
render(<FileBrowser uploadUrl={createUploadUrl(apiBase, 'browser_test', '')} rootTitle={browserConfig.rootTitle}/>)
await waitFor(() => {
expect(screen.getByText('Root Title')).toBeVisible()
})
......@@ -165,7 +167,7 @@ test('starting in entry dir', async () => {
browserTree: fileBrowserTreeModified,
editable: true
}
render(<FileBrowser uploadId="browser_test" path={entryDir} rootTitle={browserConfig.rootTitle}/>)
render(<FileBrowser uploadUrl={createUploadUrl(apiBase, 'browser_test', entryDir)} rootTitle={browserConfig.rootTitle}/>)
await waitFor(() => {
expect(screen.getByText('Root Title')).toBeVisible()
})
......@@ -187,7 +189,7 @@ test('delete files', async () => {
browserTree: fileBrowserTreeCopy,
editable: true
}
render(<FileBrowser uploadId="browser_test" path="" rootTitle={browserConfig.rootTitle}/>)
render(<FileBrowser uploadUrl={createUploadUrl(apiBase, 'browser_test', '')} rootTitle={browserConfig.rootTitle}/>)
await waitFor(() => {
expect(screen.getByText('Root Title')).toBeVisible()
})
......@@ -215,7 +217,7 @@ test('delete folder', async () => {
browserTree: fileBrowserTreeCopy,
editable: true
}
render(<FileBrowser uploadId="browser_test" path="" rootTitle={browserConfig.rootTitle}/>)
render(<FileBrowser uploadUrl={createUploadUrl(apiBase, 'browser_test', '')} rootTitle={browserConfig.rootTitle}/>)
await waitFor(() => {
expect(screen.getByText('Root Title')).toBeVisible()
})
......
......@@ -22,6 +22,8 @@ import FileBrowser from '../archive/FileBrowser'
import { useApi } from '../api'
import Page from '../Page'
import { useEntryPageContext } from './EntryPageContext'
import { createUploadUrl } from '../../utils'
import { apiBase } from '../../config'
const useStyles = makeStyles(theme => ({
error: {
......@@ -66,8 +68,7 @@ const BrowseEntryFilesView = React.memo((props) => {
const mainfileBasename = data.mainfile.split('/').pop()
return <Page>
<FileBrowser
uploadId={data.upload_id}
path={mainfileDirname}
uploadUrl={createUploadUrl(apiBase, data.upload_id, mainfileDirname)} // TODO: installationUrl should come from entry context
rootTitle="Entry files"
highlightedItem={mainfileBasename}
/>
......
......@@ -19,15 +19,13 @@ import React, { useState, useCallback } from 'react'
import PropTypes from 'prop-types'
import { Tooltip, IconButton, Dialog, DialogContent, DialogContentText, DialogActions, Button } from '@material-ui/core'
import DeleteIcon from '@material-ui/icons/Delete'
import { useDataStore } from '../DataStore'
import { useUploadPageContext } from './UploadPageContext'
import {useApi} from '../api'
import {useErrors} from '../errors'