Skip to content

Commit

Permalink
Bundle store details page (#3860)
Browse files Browse the repository at this point in the history
* fix error message

* add bundle store cli commands

* added store view

* fix text

* fix test

* Update codalab/lib/bundle_cli.py

Co-authored-by: Ashwin Ramaswami <[email protected]>

* Remove replicate for now

* Revert "fix error message"

This reverts commit eeaccd7.

* Fix endpoints to use bulk, add "cl store add" impl

* refresh

* more impl for bundle store commands

* update

* fix

* Add deletion, support deletion logic

* Fix delete

* Support "cl upload --store"

* Update references

* finishing up

* Add name field

* Update bundles.py

Co-authored-by: Ashwin Ramaswami <[email protected]>
  • Loading branch information
jzwang43 and epicfaace authored Dec 14, 2021
1 parent c2514fa commit 8f7d1e6
Show file tree
Hide file tree
Showing 8 changed files with 383 additions and 17 deletions.
2 changes: 1 addition & 1 deletion codalab/model/bundle_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3040,7 +3040,7 @@ def get_bundle_store(self, user_id: int, uuid: str = None, name: str = None) ->
).fetchone()
return {
'uuid': row.uuid,
'owner': row.owner_id,
'owner_id': row.owner_id,
'name': row.name,
'storage_type': row.storage_type,
'storage_format': row.storage_format,
Expand Down
38 changes: 22 additions & 16 deletions codalab/rest/bundles.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,22 +543,28 @@ def _add_bundle_store():
# updated_bundle_store = local.model.update_bundle_store(request.user.user_id, uuid, update)
# return BundleStoreSchema(many=True).dump(updated_bundle_store).data

# TODO: Endpoint not tested / used, reenable when we use it.
# @get('/bundle_stores/<uuid:re:%s>' % spec_util.UUID_STR, apply=AuthenticatedProtectedPlugin())
# def _fetch_bundle_store(uuid):
# """
# Fetch the bundle store corresponding to the specified uuid.

# Returns a single bundle store, with the following parameters:
# - `uuid`: bundle store UUID
# - `owner_id`: owner of bundle store
# - `name`: name of bundle store
# - `storage_type`: type of storage being used for bundle store (GCP, AWS, etc)
# - `storage_format`: the format in which storage is being stored (UNCOMPRESSED, COMPRESSED_V1, etc)
# - `url`: a self-referential URL that points to the bundle store.
# """
# bundle_store = local.model.get_bundle_store(request.user.user_id, uuid)
# return BundleStoreSchema(many=True).dump(bundle_store).data

@get('/bundle_stores/<uuid:re:%s>' % spec_util.UUID_STR, apply=AuthenticatedProtectedPlugin())
def _fetch_bundle_store(uuid):
"""
Fetch the bundle store corresponding to the specified uuid.
Returns a single bundle store, with the following parameters:
- `uuid`: bundle store UUID
- `owner_id`: owner of bundle store
- `name`: name of bundle store
- `storage_type`: type of storage being used for bundle store (GCP, AWS, etc)
- `storage_format`: the format in which storage is being stored (UNCOMPRESSED, COMPRESSED_V1, etc)
- `url`: a self-referential URL that points to the bundle store.
"""
bundle_store = local.model.get_bundle_store(request.user.user_id, uuid)
data = BundleStoreSchema().dump(bundle_store).data

# Fetch username instead of user_id for display on the front end.
data['data']['attributes']['owner'] = local.model.get_user(
user_id=data['data']['attributes']['owner']
).user_name
return data


@delete('/bundle_stores', apply=AuthenticatedProtectedPlugin())
Expand Down
12 changes: 12 additions & 0 deletions docs/REST-API-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,18 @@ JSON parameters:
- `authentication`: key for authentication that the bundle store uses.
Returns the data of the created bundle store.

### `GET /bundle_stores/<uuid:re:0x[0-9a-f]{32}>`

Fetch the bundle store corresponding to the specified uuid.

Returns a single bundle store, with the following parameters:
- `uuid`: bundle store UUID
- `owner_id`: owner of bundle store
- `name`: name of bundle store
- `storage_type`: type of storage being used for bundle store (GCP, AWS, etc)
- `storage_format`: the format in which storage is being stored (UNCOMPRESSED, COMPRESSED_V1, etc)
- `url`: a self-referential URL that points to the bundle store.

### `DELETE /bundle_stores`

Delete the specified bundle stores.
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/CodalabApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import NewDashboard from './components/Dashboard/NewDashboard';
// Routes
import HomePage from './routes/HomePage';
import BundleRoute from './routes/BundleRoute';
import StoreRoute from './routes/StoreRoute';

import history from './history';
import Cookies from 'universal-cookie';
Expand Down Expand Up @@ -119,6 +120,7 @@ function CodalabApp() {
path='/users'
render={(props) => <NewDashboard {...props} auth={fakeAuth} />}
/>
<Route path='/stores/:uuid' component={StoreRoute} />
<Route component={PageNotFound} />
</Switch>
{/*Footer.*/}
Expand Down
119 changes: 119 additions & 0 deletions frontend/src/components/Store/Store.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// @flow
import * as React from 'react';
import SubHeader from '../SubHeader';
import ContentWrapper from '../ContentWrapper';
import { renderFormat } from '../../util/worksheet_utils';
import './Store.scss';
import ErrorMessage from '../worksheets/ErrorMessage';
import { fetchStores } from '../../util/apiWrapper';

class Store extends React.Component {
state = {
errorMessages: [],
storeInfo: null,
};
/**
* Fetch store data and update the state of this component.
*/
refreshStore = () => {
const { uuid } = this.props;
fetchStores(uuid)
.then((response) => {
const {
data: { attributes: storeInfo },
} = response;
console.log('storeInfo', storeInfo);

this.setState({
storeInfo,
});
})
.catch((err) => {
this.setState({ errorMessages: [err.toString()] });
});
};

componentDidMount = () => {
this.refreshStore();
};

/** Renderer. */
render = () => {
const storeInfo = this.state.storeInfo;
console.log('storeInfo', storeInfo);
if (!storeInfo) {
// Error
if (this.state.errorMessages.length > 0) {
return <ErrorMessage message={"Not found: '/stores/" + this.props.uuid + "'"} />;
}

// Still loading
return (
<div id='store-message' className='store-detail'>
<img alt='Loading' src={`${process.env.PUBLIC_URL}/img/Preloader_Small.gif`} />{' '}
Loading store info...
</div>
);
}

const storeMetadataChanged = this.refreshStore;

const content = (
<div id='panel_content'>
{renderErrorMessages(this.state.errorMessages)}
{renderHeader(storeInfo, storeMetadataChanged)}
</div>
);
return (
<div id='store-content'>
<React.Fragment>
<SubHeader title='Store View' />
<ContentWrapper>{content}</ContentWrapper>
</React.Fragment>
</div>
);
};
}

function renderErrorMessages(messages) {
return (
<div id='store-error-messages'>
{messages.map((message) => {
return <div className='alert alert-danger alert-dismissable'>{message}</div>;
})}
</div>
);
}

function createRow(key, value) {
return (
<tr key={key}>
<th>
<span>{key}</span>
</th>
<td>
<span>{value}</span>
</td>
</tr>
);
}

function renderHeader(storeInfo) {
// Display basic information.
let rows = [];
rows.push(createRow('name', storeInfo.name));
rows.push(createRow('uuid', storeInfo.uuid));
rows.push(createRow('bundle store type', storeInfo.storage_type));
rows.push(createRow('owner', storeInfo.owner === null ? '<anonymous>' : storeInfo.owner));
rows.push(createRow('location', storeInfo.url));

return (
<div>
<table className='store-meta table'>
<tbody>{rows.map((elem) => elem)}</tbody>
</table>
</div>
);
}

export default Store;
Loading

0 comments on commit 8f7d1e6

Please sign in to comment.