Add real web zip exports to the downloads page
closes OFFW-47 test plan - Load the Course Content Downloads page either via modules or going directly to /courses/:course_id/offline_web_exports - Verify that there is a real list of downloads in oldest to newest order - If there are any that are in process, they should not show up. - A spinner should show up while loading the history - If you can induce an error, you should get error text Change-Id: I21cb0d0196fdd3fda93ee0680ff076e79b798780 Reviewed-on: https://gerrit.instructure.com/98236 Tested-by: Jenkins Reviewed-by: Jon Willesen <jonw+gerrit@instructure.com> QA-Review: Nathan Rogowski <nathan@instructure.com> Product-Review: Mysti Sadler <mysti@instructure.com>
This commit is contained in:
parent
e1bf79985c
commit
a30ab6da39
|
@ -118,7 +118,7 @@ class WebZipExportsController < ApplicationController
|
|||
def index
|
||||
return unless authorized_action(@context, @current_user, :read)
|
||||
|
||||
user_web_zips = @context.web_zip_exports.visible_to(@current_user)
|
||||
user_web_zips = @context.web_zip_exports.visible_to(@current_user).order("created_at DESC")
|
||||
web_zips_json = Api.paginate(user_web_zips, self, api_v1_web_zip_exports_url).map do |web_zip|
|
||||
web_zip_export_json(web_zip)
|
||||
end
|
||||
|
|
|
@ -1,27 +1,62 @@
|
|||
define([
|
||||
'react',
|
||||
'axios',
|
||||
'instructure-ui/Spinner',
|
||||
'i18n!webzip_exports',
|
||||
'compiled/str/splitAssetString',
|
||||
'jsx/webzip_export/components/ExportList',
|
||||
], (React, I18n, ExportList) => {
|
||||
'jsx/webzip_export/components/Errors',
|
||||
], (React, axios, {default: Spinner}, I18n, splitAssetString, ExportList, Errors) => {
|
||||
class WebZipExportApp extends React.Component {
|
||||
|
||||
static datesAndLinksFromAPI (webZipExports) {
|
||||
return webZipExports.filter(webZipExport =>
|
||||
webZipExport.workflow_state === 'generated' || webZipExport.workflow_state === 'failed'
|
||||
).map((webZipExport) => {
|
||||
const url = webZipExport.zip_attachment ? webZipExport.zip_attachment.url : null
|
||||
return {date: webZipExport.created_at, link: url}
|
||||
}).reverse()
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {exports: this.fakeData()}
|
||||
this.state = {exports: [], errors: []}
|
||||
}
|
||||
|
||||
fakeData () {
|
||||
return [
|
||||
{date: 'Nov 11, 2016 @ 3:33 PM', link: 'https://example.com'},
|
||||
{date: 'Nov 15, 2016 @ 7:07 PM', link: 'https://example.com'}
|
||||
]
|
||||
componentDidMount () {
|
||||
const courseId = splitAssetString(ENV.context_asset_string)[1]
|
||||
this.loadExistingExports(courseId)
|
||||
}
|
||||
|
||||
loadExistingExports (courseId) {
|
||||
axios.get(`/api/v1/courses/${courseId}/web_zip_exports`)
|
||||
.then((response) => {
|
||||
this.setState({
|
||||
exports: WebZipExportApp.datesAndLinksFromAPI(response.data),
|
||||
errors: [],
|
||||
})
|
||||
})
|
||||
.catch((response) => {
|
||||
this.setState({
|
||||
exports: [],
|
||||
errors: [response],
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
let app = null
|
||||
if (this.state.exports.length === 0 && this.state.errors.length === 0) {
|
||||
app = (<Spinner size="small" title={I18n.t('Loading')} />)
|
||||
} else if (this.state.exports.length === 0) {
|
||||
app = (<Errors errors={this.state.errors} />)
|
||||
} else {
|
||||
app = (<ExportList exports={this.state.exports} />)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h1>{I18n.t('Course Content Downloads')}</h1>
|
||||
<ExportList exports={this.state.exports} />
|
||||
{app}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
define([
|
||||
'redux-actions',
|
||||
], (ReduxActions) => {
|
||||
const { createAction } = ReduxActions
|
||||
|
||||
const keys = {
|
||||
CREATE_NEW_EXPORT: 'CREATE_NEW_EXPORT'
|
||||
}
|
||||
|
||||
const actions = {
|
||||
createNewExport: createAction(keys.CREATE_NEW_EXPORT)
|
||||
}
|
||||
|
||||
return {
|
||||
actions,
|
||||
keys
|
||||
}
|
||||
})
|
|
@ -0,0 +1,14 @@
|
|||
define([
|
||||
'react',
|
||||
'i18n!webzip_exports',
|
||||
], (React, I18n) => {
|
||||
const Errors = (props) => {
|
||||
return (
|
||||
<p className="webzipexport__errors">
|
||||
{I18n.t('An error occurred. Please try again later.')}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
return Errors
|
||||
})
|
|
@ -4,13 +4,16 @@ define([
|
|||
], (React, ExportListItem) => {
|
||||
class ExportList extends React.Component {
|
||||
static propTypes = {
|
||||
exports: React.PropTypes.array.isRequired
|
||||
exports: React.PropTypes.arrayOf(React.PropTypes.shape({
|
||||
date: React.PropTypes.string.isRequired,
|
||||
link: React.PropTypes.string.isRequired,
|
||||
})).isRequired
|
||||
}
|
||||
|
||||
renderExportListItems () {
|
||||
return this.props.exports.map(function (webzip, key) {
|
||||
return <ExportListItem key={key} link={webzip.link} date={webzip.date} />
|
||||
})
|
||||
return this.props.exports.map((webzip, key) =>
|
||||
<ExportListItem key={key} link={webzip.link} date={webzip.date} />
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
|
|
|
@ -3,7 +3,9 @@ define([
|
|||
'underscore',
|
||||
'jsx/shared/ApiProgressBar',
|
||||
'i18n!webzip_exports',
|
||||
], (React, _, ApiProgressBar, I18n) => {
|
||||
'jquery',
|
||||
'jquery.instructure_date_and_time'
|
||||
], (React, _, ApiProgressBar, I18n, $) => {
|
||||
class ExportListItem extends React.Component {
|
||||
static propTypes = {
|
||||
date: React.PropTypes.string.isRequired,
|
||||
|
@ -14,7 +16,7 @@ define([
|
|||
return (
|
||||
<li className={'webzipexport__list__item'}>
|
||||
<span>{I18n.t('Course Content Download from')}</span>
|
||||
<span>: <a href={this.props.link}>{this.props.date}</a></span>
|
||||
<span>: <a href={this.props.link}>{$.datetimeString(this.props.date)}</a></span>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
define([
|
||||
'redux-actions',
|
||||
'./actions',
|
||||
], (ReduxActions, Actions) => {
|
||||
const { handleActions } = ReduxActions
|
||||
|
||||
const reducer = handleActions({
|
||||
[Actions.keys.CREATE_NEW_EXPORT]: (state = {}, action) => ({
|
||||
exports: [
|
||||
...state.exports,
|
||||
{date: action.payload.date, link: action.payload.link}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
return reducer
|
||||
})
|
|
@ -1,19 +0,0 @@
|
|||
define([
|
||||
'redux',
|
||||
'redux-thunk',
|
||||
'redux-logger',
|
||||
'../reducer',
|
||||
], (Redux, {default: ReduxThunk}, reduxLogger, reducer) => {
|
||||
const { createStore, applyMiddleware } = Redux
|
||||
|
||||
const logger = reduxLogger()
|
||||
|
||||
const createStoreWithMiddleware = applyMiddleware(
|
||||
logger,
|
||||
ReduxThunk
|
||||
)(createStore)
|
||||
|
||||
return function configureStore (state = {}) {
|
||||
return createStoreWithMiddleware(reducer, state)
|
||||
}
|
||||
})
|
|
@ -0,0 +1,102 @@
|
|||
define([
|
||||
'react',
|
||||
'enzyme',
|
||||
'moxios',
|
||||
'jsx/webzip_export/App',
|
||||
], (React, enzyme, moxios, WebZipExportApp) => {
|
||||
module('WebZip Export App', {
|
||||
setup () {
|
||||
moxios.install()
|
||||
},
|
||||
teardown () {
|
||||
moxios.uninstall()
|
||||
}
|
||||
})
|
||||
|
||||
test('renders a spinner before API call', () => {
|
||||
const wrapper = enzyme.shallow(<WebZipExportApp />)
|
||||
const node = wrapper.find('Spinner')
|
||||
ok(node.exists())
|
||||
})
|
||||
|
||||
test('renders a list of webzip exports', (assert) => {
|
||||
const done = assert.async()
|
||||
const data = [{
|
||||
created_at: '1776-12-25T22:00:00Z',
|
||||
zip_attachment: {url: 'http://example.com/washingtoncrossingdelaware'},
|
||||
workflow_state: 'generated',
|
||||
}]
|
||||
moxios.stubRequest('/api/v1/courses/2/web_zip_exports', {
|
||||
status: 200,
|
||||
responseText: data
|
||||
})
|
||||
ENV.context_asset_string = 'courses_2'
|
||||
const wrapper = enzyme.shallow(<WebZipExportApp />)
|
||||
wrapper.instance().componentDidMount()
|
||||
moxios.wait(() => {
|
||||
const node = wrapper.find('ExportList')
|
||||
ok(node.exists())
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
test('renders errors', (assert) => {
|
||||
const done = assert.async()
|
||||
moxios.stubRequest('/api/v1/courses/2/web_zip_exports', {
|
||||
status: 666,
|
||||
responseText: 'Demons!'
|
||||
})
|
||||
ENV.context_asset_string = 'courses_2'
|
||||
const wrapper = enzyme.shallow(<WebZipExportApp />)
|
||||
wrapper.instance().componentDidMount()
|
||||
moxios.wait(() => {
|
||||
const node = wrapper.find('Errors')
|
||||
ok(node.exists())
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
module('datesAndLinksFromAPI')
|
||||
|
||||
test('returns a JS object with webzip created_at dates and webzip export attachment urls', () => {
|
||||
const data = [{
|
||||
created_at: '2017-01-03T15:55Z',
|
||||
zip_attachment: {url: 'http://example.com/stuff'},
|
||||
workflow_state: 'generated',
|
||||
},
|
||||
{
|
||||
created_at: '1776-12-25T22:00Z',
|
||||
zip_attachment: {url: 'http://example.com/washingtoncrossingdelaware'},
|
||||
workflow_state: 'generated',
|
||||
}]
|
||||
const formatted = WebZipExportApp.datesAndLinksFromAPI(data)
|
||||
const expected = [{
|
||||
date: '1776-12-25T22:00Z',
|
||||
link: 'http://example.com/washingtoncrossingdelaware'
|
||||
},
|
||||
{
|
||||
date: '2017-01-03T15:55Z',
|
||||
link: 'http://example.com/stuff'
|
||||
}]
|
||||
deepEqual(formatted, expected)
|
||||
})
|
||||
|
||||
test('does not include items with a workflow_state other than generated', () => {
|
||||
const data = [{
|
||||
created_at: '2017-01-03T15:55Z',
|
||||
zip_attachment: {url: 'http://example.com/stuff'},
|
||||
workflow_state: 'generating',
|
||||
},
|
||||
{
|
||||
created_at: '1776-12-25T22:00Z',
|
||||
zip_attachment: {url: 'http://example.com/washingtoncrossingdelaware'},
|
||||
workflow_state: 'generated',
|
||||
}]
|
||||
const formatted = WebZipExportApp.datesAndLinksFromAPI(data)
|
||||
const expected = [{
|
||||
date: '1776-12-25T22:00Z',
|
||||
link: 'http://example.com/washingtoncrossingdelaware'
|
||||
}]
|
||||
deepEqual(formatted, expected)
|
||||
})
|
||||
})
|
|
@ -1,16 +0,0 @@
|
|||
define([
|
||||
'jsx/webzip_export/actions',
|
||||
], (Actions) => {
|
||||
module('WebZip Export Actions');
|
||||
|
||||
test('createNewExport returns the proper action', () => {
|
||||
const payload = {date: 'July 20, 1969 @ 20:18 UTC', link: 'http://example.com/manonthemoon'}
|
||||
const actual = Actions.actions.createNewExport(payload);
|
||||
const expected = {
|
||||
type: 'CREATE_NEW_EXPORT',
|
||||
payload
|
||||
};
|
||||
|
||||
deepEqual(actual, expected);
|
||||
})
|
||||
})
|
|
@ -0,0 +1,15 @@
|
|||
define([
|
||||
'react',
|
||||
'react-dom',
|
||||
'enzyme',
|
||||
'jsx/webzip_export/components/Errors',
|
||||
], (React, ReactDOM, enzyme, Errors) => {
|
||||
module('Web Zip Export Errors')
|
||||
|
||||
test('renders the Error component', () => {
|
||||
const errors = [{response: 'Instance of demon found in code', code: 666}];
|
||||
const tree = enzyme.shallow(<Errors errors={errors} />)
|
||||
const node = tree.find('.webzipexport__errors')
|
||||
ok(node.exists())
|
||||
})
|
||||
})
|
|
@ -7,7 +7,7 @@ define([
|
|||
module('ExportListItem')
|
||||
|
||||
test('renders the ExportListItem component', () => {
|
||||
const date = 'Sept 11, 2001 @ 8:46 AM'
|
||||
const date = 'Sept 11, 2001 at 8:46am'
|
||||
const link = 'https://example.com/neverforget'
|
||||
|
||||
const tree = TestUtils.renderIntoDocument(<ExportListItem date={date} link={link} />)
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
define([
|
||||
'react',
|
||||
'react-dom',
|
||||
'react-addons-test-utils',
|
||||
'enzyme',
|
||||
'jsx/webzip_export/components/ExportList',
|
||||
], (React, ReactDOM, TestUtils, ExportList) => {
|
||||
], (React, ReactDOM, enzyme, ExportList) => {
|
||||
module('ExportList')
|
||||
|
||||
test('renders the ExportList component', () => {
|
||||
const exports = [
|
||||
{date: 'July 4, 1776 @ 3:33 PM', link: 'https://example.com/declarationofindependence'},
|
||||
{date: 'Nov 9, 1989 @ 9:00 AM', link: 'https://example.com/berlinwallfalls'}
|
||||
{date: 'July 4, 1776 at 3:33pm', link: 'https://example.com/declarationofindependence'},
|
||||
{date: 'Nov 9, 1989 at 9am', link: 'https://example.com/berlinwallfalls'}
|
||||
]
|
||||
|
||||
const tree = TestUtils.renderIntoDocument(<ExportList exports={exports} />)
|
||||
const ExportListComponent = TestUtils.findRenderedDOMComponentWithClass(tree, 'webzipexport__list')
|
||||
ok(ExportListComponent)
|
||||
const tree = enzyme.shallow(<ExportList exports={exports} />)
|
||||
const node = tree.find('.webzipexport__list')
|
||||
ok(node.exists())
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
define([
|
||||
'jsx/webzip_export/reducer'
|
||||
], (reducer) => {
|
||||
module('Webzip Exports Reducer')
|
||||
|
||||
test('creates new export on CREATE_NEW_EXPORT', () => {
|
||||
const initialState = {
|
||||
exports: [{date: 'December 7, 1941 @ 8:00 AM', link: 'http://example.com/pearlharbor'}]
|
||||
}
|
||||
|
||||
const newState = reducer(initialState, {
|
||||
type: 'CREATE_NEW_EXPORT',
|
||||
payload: {date: 'December 25, 1776 @ 10:00 PM', link: 'http://example.com/washingtoncrossingdelaware'}
|
||||
})
|
||||
|
||||
equal(newState.exports.length, 2)
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue