IM: write file as icon when viewing from All Files
Selecting an icon maker file from the side tray when viewing "All Files", its inserted into the RCE as an icon maker icon fixes MAT-987 flag=buttons_and_icons_root_account flag=buttons_and_icons_cropper * Will also need the following canvas-rce-api commit running locally: https://gerrit.instructure.com/c/canvas-rce-api/+/302455 ** Check that commit first on how to test the RCS for backwards compatibility. This backwards compatibility test should be done before getting these changes into your local dev environment. Test Plan - Go to the RCE and select "Saved Icon Maker Icons" from the IM button menu - Select the "Icon Maker Icons" dropdown in the side tray and change it to "All" - Navigate to Course Files > Icon Maker Icons - Select an icon from the list to insert it into the RCE - Select the newly added icon in the RCE * VERIFY: 1. The newly added icon has IM-only context menus: "Edit Icon" and "Icon Options" 2. Edit icon and saving changes works as expected 3. "Icon Options" allows for changing alt text settings 4. Also verify that adding non-Icon-Maker-icons works as before using All Files. E.g. add an image, select the image in the RCE and you should get the "Image Options" context menu item. Change-Id: I862ce51021955624b354a3dfe7f24db8251209b2 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/302435 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Jake Oeding <jake.oeding@instructure.com> QA-Review: Jake Oeding <jake.oeding@instructure.com> Product-Review: David Lyons <lyons@instructure.com>
This commit is contained in:
parent
8bd46cb657
commit
f886aadd76
|
@ -30,7 +30,7 @@ module Api::V1::Folders
|
|||
def folder_json(folder, user, session, opts = {})
|
||||
can_view_hidden_files = opts.key?(:can_view_hidden_files) ? opts[:can_view_hidden_files] : folder.grants_right?(user, :update)
|
||||
json = api_json(folder, user, session,
|
||||
only: %w[id name full_name position parent_folder_id context_type context_id unlock_at lock_at created_at updated_at])
|
||||
only: %w[id name full_name position parent_folder_id context_type context_id unlock_at lock_at created_at updated_at category])
|
||||
if folder
|
||||
if opts[:master_course_restricted_folder_ids]&.include?(folder.id)
|
||||
json["is_master_course_child_content"] = true
|
||||
|
|
|
@ -30,11 +30,10 @@ import {Footer} from './CreateIconMakerForm/Footer'
|
|||
import {buildStylesheet, buildSvg} from '../svg'
|
||||
import {statuses, useSvgSettings} from '../svg/settings'
|
||||
import {defaultState, actions} from '../reducers/svgSettings'
|
||||
import {ICON_MAKER_ATTRIBUTE, ICON_MAKER_DOWNLOAD_URL_ATTR} from '../svg/constants'
|
||||
import {FixedContentTray} from '../../shared/FixedContentTray'
|
||||
import {useStoreProps} from '../../shared/StoreContext'
|
||||
import formatMessage from '../../../../format-message'
|
||||
import buildDownloadUrl from '../../shared/buildDownloadUrl'
|
||||
import addIconMakerAttributes from '../utils/addIconMakerAttributes'
|
||||
import {validIcon} from '../utils/iconValidation'
|
||||
import {IconMakerFormHasChanges} from '../utils/IconMakerFormHasChanges'
|
||||
import bridge from '../../../../bridge'
|
||||
|
@ -240,7 +239,6 @@ export function IconMakerTray({editor, onUnmount, editing, rcsConfig}) {
|
|||
|
||||
const writeIconToRCE = ({url, display_name}) => {
|
||||
const {alt, isDecorative, externalStyle, externalWidth, externalHeight} = settings
|
||||
|
||||
const imageAttributes = {
|
||||
alt_text: alt,
|
||||
display_name,
|
||||
|
@ -255,12 +253,7 @@ export function IconMakerTray({editor, onUnmount, editing, rcsConfig}) {
|
|||
}
|
||||
|
||||
// Mark the image as an icon maker icon.
|
||||
imageAttributes[ICON_MAKER_ATTRIBUTE] = true
|
||||
|
||||
// URL to fetch the SVG from when loading the Edit tray.
|
||||
// We can't use the 'src' because Canvas will re-write the
|
||||
// source attribute to a URL that is not cross-origin friendly.
|
||||
imageAttributes[ICON_MAKER_DOWNLOAD_URL_ATTR] = buildDownloadUrl(url)
|
||||
addIconMakerAttributes(imageAttributes)
|
||||
|
||||
bridge.embedImage(imageAttributes)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import addIconMakerAttributes from '../addIconMakerAttributes'
|
||||
import {ICON_MAKER_ATTRIBUTE, ICON_MAKER_DOWNLOAD_URL_ATTR} from '../../svg/constants'
|
||||
import buildDownloadUrl from '../../../shared/buildDownloadUrl'
|
||||
|
||||
describe('addIconMakerAttributes', () => {
|
||||
const url = 'http://canvas.tests/files/1/download'
|
||||
let imageAttributes
|
||||
beforeEach(() => {
|
||||
imageAttributes = {src: url}
|
||||
})
|
||||
|
||||
it('adds the "data-inst-icon-maker-icon" attribute with a value of "true"', () => {
|
||||
addIconMakerAttributes(imageAttributes)
|
||||
expect(imageAttributes[ICON_MAKER_ATTRIBUTE]).toBe(true)
|
||||
})
|
||||
|
||||
it('adds the "data-download-url" attribute with the correct url for IM icons', () => {
|
||||
addIconMakerAttributes(imageAttributes)
|
||||
const expectedDownloadUrl = buildDownloadUrl(imageAttributes.src)
|
||||
expect(imageAttributes[ICON_MAKER_DOWNLOAD_URL_ATTR]).toBe(expectedDownloadUrl)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {ICON_MAKER_ATTRIBUTE, ICON_MAKER_DOWNLOAD_URL_ATTR} from '../svg/constants'
|
||||
import buildDownloadUrl from '../../shared/buildDownloadUrl'
|
||||
|
||||
const addIconMakerAttributes = (imageAttributes: {src: string}) => {
|
||||
imageAttributes[ICON_MAKER_ATTRIBUTE] = true
|
||||
imageAttributes[ICON_MAKER_DOWNLOAD_URL_ATTR] = buildDownloadUrl(imageAttributes.src)
|
||||
}
|
||||
export default addIconMakerAttributes
|
|
@ -23,6 +23,7 @@ import {View} from '@instructure/ui-view'
|
|||
import {downloadToWrap} from '../../../common/fileUrl'
|
||||
import {mediaPlayerURLFromFile} from './fileTypeUtils'
|
||||
import RceApiSource from '../../../rcs/api'
|
||||
import addIconMakerAttributes from '../instructure_icon_maker/utils/addIconMakerAttributes'
|
||||
|
||||
// TODO: should find a better way to share this code
|
||||
import FileBrowser from '../../../canvasFileBrowser/FileBrowser'
|
||||
|
@ -31,7 +32,7 @@ import {isPreviewable} from './Previewable'
|
|||
RceFileBrowser.propTypes = {
|
||||
onFileSelect: func.isRequired,
|
||||
onAllFilesLoading: func.isRequired,
|
||||
searchString: string.isRequired
|
||||
searchString: string.isRequired,
|
||||
}
|
||||
|
||||
export default function RceFileBrowser(props) {
|
||||
|
@ -42,7 +43,7 @@ export default function RceFileBrowser(props) {
|
|||
new RceApiSource({
|
||||
jwt,
|
||||
refreshToken,
|
||||
host
|
||||
host,
|
||||
})
|
||||
)
|
||||
}, [host, jwt, refreshToken, source])
|
||||
|
@ -51,24 +52,31 @@ export default function RceFileBrowser(props) {
|
|||
const content_type = fileInfo.api.type
|
||||
const canPreview = isPreviewable(content_type)
|
||||
|
||||
const clazz = classnames('instructure_file_link', {
|
||||
instructure_scribd_file: canPreview,
|
||||
inline_disabled: true
|
||||
})
|
||||
|
||||
const url = downloadToWrap(fileInfo.src)
|
||||
const embedded_iframe_url = mediaPlayerURLFromFile(fileInfo.api)
|
||||
|
||||
onFileSelect({
|
||||
let onFileSelectParams = {
|
||||
name: fileInfo.name,
|
||||
title: fileInfo.name,
|
||||
href: url,
|
||||
embedded_iframe_url,
|
||||
media_id: fileInfo.api.embed?.id,
|
||||
target: '_blank',
|
||||
class: clazz,
|
||||
content_type
|
||||
})
|
||||
content_type,
|
||||
}
|
||||
if (fileInfo.api?.category === 'icon_maker_icons') {
|
||||
onFileSelectParams.src = fileInfo.api.url
|
||||
addIconMakerAttributes(onFileSelectParams)
|
||||
} else {
|
||||
// do not add this to icon maker icons
|
||||
const clazz = classnames('instructure_file_link', {
|
||||
instructure_scribd_file: canPreview,
|
||||
inline_disabled: true,
|
||||
})
|
||||
onFileSelectParams = {...onFileSelectParams, class: clazz}
|
||||
}
|
||||
|
||||
onFileSelect(onFileSelectParams)
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -24,19 +24,22 @@ import FileBrowser from '../../../../canvasFileBrowser/FileBrowser'
|
|||
jest.mock('../../../../canvasFileBrowser/FileBrowser', () => {
|
||||
return jest.fn(() => 'Files Browser')
|
||||
})
|
||||
jest.mock('../../../../bridge')
|
||||
|
||||
describe('RceFileBrowser', () => {
|
||||
const onAllFilesLoading = jest.fn()
|
||||
const props = {searchString: '', onAllFilesLoading}
|
||||
afterEach(() => FileBrowser.mockClear())
|
||||
|
||||
it('invokes onFileSelect callback with appropriate data when a file is selected', () => {
|
||||
const onFileSelect = jest.fn()
|
||||
render(<RceFileBrowser onFileSelect={onFileSelect} />)
|
||||
render(<RceFileBrowser onFileSelect={onFileSelect} {...props} />)
|
||||
// This is the selectFile prop passed to the Canvas FileBrowser that we mocked above
|
||||
const selectFile = FileBrowser.mock.calls[0][0].selectFile
|
||||
selectFile({
|
||||
name: 'a file',
|
||||
src: '/file/download',
|
||||
api: {url: '/file/download?download_frd=1', type: 'application/pdf'}
|
||||
api: {url: '/file/download?download_frd=1', type: 'application/pdf'},
|
||||
})
|
||||
expect(onFileSelect).toHaveBeenCalledWith({
|
||||
name: 'a file',
|
||||
|
@ -45,13 +48,13 @@ describe('RceFileBrowser', () => {
|
|||
embedded_iframe_url: undefined,
|
||||
content_type: 'application/pdf',
|
||||
target: '_blank',
|
||||
class: 'instructure_file_link instructure_scribd_file inline_disabled'
|
||||
class: 'instructure_file_link instructure_scribd_file inline_disabled',
|
||||
})
|
||||
})
|
||||
|
||||
it('plumbs the media_id when a video file is selected', () => {
|
||||
const onFileSelect = jest.fn()
|
||||
render(<RceFileBrowser onFileSelect={onFileSelect} />)
|
||||
render(<RceFileBrowser onFileSelect={onFileSelect} {...props} />)
|
||||
// This is the selectFile prop passed to the Canvas FileBrowser that we mocked above
|
||||
const selectFile = FileBrowser.mock.calls[0][0].selectFile
|
||||
selectFile({
|
||||
|
@ -60,8 +63,8 @@ describe('RceFileBrowser', () => {
|
|||
api: {
|
||||
url: '/file/download?download_frd=1',
|
||||
type: 'video/mp4',
|
||||
embed: { id: 'm-deadbeef' }
|
||||
}
|
||||
embed: {id: 'm-deadbeef'},
|
||||
},
|
||||
})
|
||||
expect(onFileSelect).toHaveBeenCalledWith({
|
||||
name: 'a video',
|
||||
|
@ -71,7 +74,34 @@ describe('RceFileBrowser', () => {
|
|||
media_id: 'm-deadbeef',
|
||||
content_type: 'video/mp4',
|
||||
target: '_blank',
|
||||
class: 'instructure_file_link inline_disabled'
|
||||
class: 'instructure_file_link inline_disabled',
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the selected file has category=icon_maker_icon', () => {
|
||||
it('adds icon maker icon attributes to onFileSelect object param', () => {
|
||||
const onFileSelect = jest.fn()
|
||||
render(<RceFileBrowser onFileSelect={onFileSelect} {...props} />)
|
||||
// This is the selectFile prop passed to the Canvas FileBrowser that we mocked above
|
||||
const selectFile = FileBrowser.mock.calls[0][0].selectFile
|
||||
const fullUrl = 'http://dev.env/files/123/download'
|
||||
selectFile({
|
||||
name: 'a file',
|
||||
src: '/files/123/download',
|
||||
api: {
|
||||
url: fullUrl,
|
||||
name: 'awesome-icon',
|
||||
category: 'icon_maker_icons',
|
||||
type: 'image/jpg',
|
||||
},
|
||||
})
|
||||
expect(onFileSelect).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
'data-download-url': `${fullUrl}?icon_maker_icon=1`,
|
||||
'data-inst-icon-maker-icon': true,
|
||||
src: fullUrl,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -27,7 +27,7 @@ beforeEach(() => {
|
|||
refreshToken: callback => {
|
||||
callback('freshJWT')
|
||||
},
|
||||
alertFunc: jest.fn()
|
||||
alertFunc: jest.fn(),
|
||||
})
|
||||
|
||||
apiSource.fetchPage = jest.fn()
|
||||
|
@ -44,9 +44,9 @@ describe('fetchImages()', () => {
|
|||
const standardProps = {
|
||||
contextType: 'course',
|
||||
images: {
|
||||
course: {}
|
||||
course: {},
|
||||
},
|
||||
sortBy: 'date'
|
||||
sortBy: 'date',
|
||||
}
|
||||
|
||||
const subject = () => apiSource.fetchImages(props)
|
||||
|
@ -59,7 +59,7 @@ describe('fetchImages()', () => {
|
|||
describe('with "category" set', () => {
|
||||
props = {
|
||||
category: 'uncategorized',
|
||||
...standardProps
|
||||
...standardProps,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -83,9 +83,26 @@ describe('fetchFilesForFolder()', () => {
|
|||
fetchMock.mock('/api/files', '{"files": []}')
|
||||
})
|
||||
|
||||
it('includes the "uncategorized" category in the request', async () => {
|
||||
it('fetches folder files without query params if none supplied in props', async () => {
|
||||
apiProps = {...apiProps}
|
||||
await subject()
|
||||
expect(apiSource.fetchPage).toHaveBeenCalledWith('/api/files?&category=uncategorized', 'theJWT')
|
||||
expect(apiSource.fetchPage).toHaveBeenCalledWith('/api/files', 'theJWT')
|
||||
})
|
||||
|
||||
it('fetches folder files using the per_page query param', async () => {
|
||||
apiProps = {...apiProps, perPage: 5}
|
||||
await subject()
|
||||
expect(apiSource.fetchPage).toHaveBeenCalledWith('/api/files?per_page=5', 'theJWT')
|
||||
})
|
||||
|
||||
it('fetches folder files using the encoded searchString query param', async () => {
|
||||
apiProps = {...apiProps, perPage: 5, searchString: 'an awesome file'}
|
||||
const encodedSearchString = encodeURIComponent(apiProps.searchString)
|
||||
await subject()
|
||||
expect(apiSource.fetchPage).toHaveBeenCalledWith(
|
||||
`/api/files?per_page=5&search_term=${encodedSearchString}`,
|
||||
'theJWT'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -102,9 +119,9 @@ describe('fetchMedia', () => {
|
|||
media: {course: {}},
|
||||
sortBy: {
|
||||
sort: 'name',
|
||||
dir: 'asc'
|
||||
dir: 'asc',
|
||||
},
|
||||
contextId: 1
|
||||
contextId: 1,
|
||||
}
|
||||
|
||||
apiSource.apiFetch = jest.fn()
|
||||
|
@ -133,8 +150,8 @@ describe('saveClosedCaptions()', () => {
|
|||
{
|
||||
language: {selectedOptionId: 'en'},
|
||||
file: new Blob(['file contents'], {type: 'text/plain'}),
|
||||
isNew: true
|
||||
}
|
||||
isNew: true,
|
||||
},
|
||||
]
|
||||
maxBytes = undefined
|
||||
})
|
||||
|
@ -148,7 +165,7 @@ describe('saveClosedCaptions()', () => {
|
|||
await subject()
|
||||
expect(apiSource.alertFunc).toHaveBeenCalledWith({
|
||||
text: 'Closed caption file must be less than 0.005 kb',
|
||||
variant: 'error'
|
||||
variant: 'error',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -20,9 +20,7 @@ import 'isomorphic-fetch'
|
|||
import {parse} from 'url'
|
||||
import {saveClosedCaptions, CONSTANTS} from '@instructure/canvas-media'
|
||||
import {downloadToWrap, fixupFileUrl} from '../common/fileUrl'
|
||||
import formatMessage from '../format-message'
|
||||
import alertHandler from '../rce/alertHandler'
|
||||
import {DEFAULT_FILE_CATEGORY} from '../sidebar/containers/sidebarHandlers'
|
||||
import buildError from './buildError'
|
||||
|
||||
export function headerFor(jwt) {
|
||||
|
@ -65,7 +63,7 @@ function normalizeFileData(file) {
|
|||
display_name: file.name,
|
||||
...file,
|
||||
// wrap the url
|
||||
href: downloadToWrap(file.href || file.url)
|
||||
href: downloadToWrap(file.href || file.url),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,7 +106,7 @@ class RceApiSource {
|
|||
bookmark: this.uriFor(endpoint, props),
|
||||
isLoading: false,
|
||||
hasMore: true,
|
||||
searchString: props.searchString
|
||||
searchString: props.searchString,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,7 +114,7 @@ class RceApiSource {
|
|||
return {
|
||||
uploading: false,
|
||||
folders: {},
|
||||
formExpanded: false
|
||||
formExpanded: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,9 +128,9 @@ class RceApiSource {
|
|||
files: [],
|
||||
bookmark: null,
|
||||
isLoading: false,
|
||||
hasMore: true
|
||||
hasMore: true,
|
||||
},
|
||||
searchString: ''
|
||||
searchString: '',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,7 +142,7 @@ class RceApiSource {
|
|||
return {
|
||||
searchResults: [],
|
||||
searching: false,
|
||||
formExpanded: false
|
||||
formExpanded: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,7 +170,7 @@ class RceApiSource {
|
|||
return this.apiFetch(uri, headerFor(this.jwt)).then(({bookmark, files}) => {
|
||||
return {
|
||||
bookmark,
|
||||
files: files.map(f => fixupFileUrl(props.contextType, props.contextId, f))
|
||||
files: files.map(f => fixupFileUrl(props.contextType, props.contextId, f)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -187,7 +185,7 @@ class RceApiSource {
|
|||
return this.fetchPage(uri).then(({bookmark, files}) => {
|
||||
return {
|
||||
bookmark,
|
||||
files: files.map(normalizeFileData)
|
||||
files: files.map(normalizeFileData),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -215,7 +213,7 @@ class RceApiSource {
|
|||
: 'video',
|
||||
context_code: mediaObject.contextCode,
|
||||
title: mediaObject.title,
|
||||
user_entered_title: mediaObject.userTitle
|
||||
user_entered_title: mediaObject.userTitle,
|
||||
}
|
||||
|
||||
return this.apiPost(this.baseUri('media_objects'), headerFor(this.jwt), body)
|
||||
|
@ -237,7 +235,7 @@ class RceApiSource {
|
|||
subtitles,
|
||||
{
|
||||
origin: originFromHost(apiProps.host),
|
||||
headers: headerFor(apiProps.jwt)
|
||||
headers: headerFor(apiProps.jwt),
|
||||
},
|
||||
maxBytes || CONSTANTS.CC_FILE_MAX_BYTES
|
||||
).catch(e => {
|
||||
|
@ -251,7 +249,7 @@ class RceApiSource {
|
|||
fetchClosedCaptions(_mediaId) {
|
||||
return Promise.resolve([
|
||||
{locale: 'af', content: '1\r\n00:00:00,000 --> 00:00:01,251\r\nThis is the content\r\n'},
|
||||
{locale: 'es', content: '1\r\n00:00:00,000 --> 00:00:01,251\r\nThis is the content\r\n'}
|
||||
{locale: 'es', content: '1\r\n00:00:00,000 --> 00:00:01,251\r\nThis is the content\r\n'},
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -268,10 +266,13 @@ class RceApiSource {
|
|||
|
||||
if (!bookmark) {
|
||||
const perPageQuery = props.perPage ? `per_page=${props.perPage}` : ''
|
||||
const categoryQuery = `category=${DEFAULT_FILE_CATEGORY}`
|
||||
uri = `${props.filesUrl}?${perPageQuery}&${categoryQuery}${getSearchParam(
|
||||
props.searchString
|
||||
)}`
|
||||
const searchParam = getSearchParam(props.searchString)
|
||||
|
||||
uri = `${props.filesUrl}`
|
||||
uri += perPageQuery ? `?${perPageQuery}` : ''
|
||||
if (searchParam) {
|
||||
uri += perPageQuery ? `${searchParam}` : `?${searchParam}`
|
||||
}
|
||||
|
||||
if (props.sortBy) {
|
||||
uri += `${getSortParams(props.sortBy.sort, props.sortBy.order)}`
|
||||
|
@ -291,7 +292,7 @@ class RceApiSource {
|
|||
contextId,
|
||||
contextType,
|
||||
host: this.host,
|
||||
jwt: this.jwt
|
||||
jwt: this.jwt,
|
||||
})
|
||||
return this.fetchPage(uri)
|
||||
}
|
||||
|
@ -319,7 +320,7 @@ class RceApiSource {
|
|||
return {
|
||||
bookmark,
|
||||
files: files.map(f => fixupFileUrl(props.contextType, props.contextId, f)),
|
||||
searchString: props.searchString
|
||||
searchString: props.searchString,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -333,7 +334,7 @@ class RceApiSource {
|
|||
file: fileProps,
|
||||
no_redirect: true,
|
||||
onDuplicate: apiProps.onDuplicate,
|
||||
category: apiProps.category
|
||||
category: apiProps.category,
|
||||
}
|
||||
|
||||
return this.apiPost(uri, headers, body)
|
||||
|
@ -433,7 +434,7 @@ class RceApiSource {
|
|||
|
||||
const uri = this.addParamsIfPresent(`${base}/${id}`, {
|
||||
replacement_chain_context_type,
|
||||
replacement_chain_context_id
|
||||
replacement_chain_context_id,
|
||||
})
|
||||
|
||||
return this.apiFetch(uri, headers).then(normalizeFileData)
|
||||
|
@ -497,7 +498,7 @@ class RceApiSource {
|
|||
headers = {...headers, 'Content-Type': 'application/json'}
|
||||
const fetchOptions = {
|
||||
method,
|
||||
headers
|
||||
headers,
|
||||
}
|
||||
if (body) {
|
||||
fetchOptions.body = JSON.stringify(body)
|
||||
|
|
|
@ -38,7 +38,7 @@ import {changeContext, changeSearchString, changeSortBy} from '../actions/filter
|
|||
import {allFilesLoading} from '../actions/all_files'
|
||||
import {get as getSession} from '../actions/session'
|
||||
|
||||
export const DEFAULT_FILE_CATEGORY = 'uncategorized'
|
||||
const DEFAULT_FILE_CATEGORY = 'uncategorized'
|
||||
|
||||
export default function propsFromDispatch(dispatch) {
|
||||
return {
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('sources/api', () => {
|
|||
contextType: 'group',
|
||||
contextId: 123,
|
||||
sortBy: {sort: 'date_added', dir: 'desc'},
|
||||
searchString: ''
|
||||
searchString: '',
|
||||
}
|
||||
let apiSource
|
||||
let alertFuncSpy
|
||||
|
@ -42,7 +42,7 @@ describe('sources/api', () => {
|
|||
refreshToken: callback => {
|
||||
callback('freshJWT')
|
||||
},
|
||||
alertFunc: alertFuncSpy
|
||||
alertFunc: alertFuncSpy,
|
||||
})
|
||||
fetchMock.mock('/api/session', '{}')
|
||||
})
|
||||
|
@ -135,7 +135,7 @@ describe('sources/api', () => {
|
|||
contextType: 'course',
|
||||
contextId: '17',
|
||||
sortBy: {sort: 'alphabetical', dir: 'asc'},
|
||||
searchString: 'hello world'
|
||||
searchString: 'hello world',
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -214,8 +214,8 @@ describe('sources/api', () => {
|
|||
bookmark: 'newBookmark',
|
||||
links: [
|
||||
{href: 'link1', title: 'Link 1'},
|
||||
{href: 'link2', title: 'Link 2'}
|
||||
]
|
||||
{href: 'link2', title: 'Link 2'},
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -289,7 +289,7 @@ describe('sources/api', () => {
|
|||
it('makes a request to the folders api with the given host and ID', () => {
|
||||
subject()
|
||||
sinon.assert.calledWith(apiSource.apiFetch, 'about://canvas.rce/api/folders/2', {
|
||||
Authorization: 'Bearer theJWT'
|
||||
Authorization: 'Bearer theJWT',
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -307,13 +307,9 @@ describe('sources/api', () => {
|
|||
|
||||
it('makes a request to the files api with given host and folder ID', () => {
|
||||
subject()
|
||||
sinon.assert.calledWith(
|
||||
apiSource.apiFetch,
|
||||
'https://canvas.rce/api/files/2?&category=uncategorized',
|
||||
{
|
||||
Authorization: 'Bearer theJWT'
|
||||
}
|
||||
)
|
||||
sinon.assert.calledWith(apiSource.apiFetch, 'https://canvas.rce/api/files/2', {
|
||||
Authorization: 'Bearer theJWT',
|
||||
})
|
||||
})
|
||||
|
||||
describe('with perPage set', () => {
|
||||
|
@ -325,9 +321,9 @@ describe('sources/api', () => {
|
|||
subject()
|
||||
sinon.assert.calledWith(
|
||||
apiSource.apiFetch,
|
||||
'https://canvas.rce/api/files/2?per_page=50&category=uncategorized',
|
||||
'https://canvas.rce/api/files/2?per_page=50',
|
||||
{
|
||||
Authorization: 'Bearer theJWT'
|
||||
Authorization: 'Bearer theJWT',
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -340,7 +336,7 @@ describe('sources/api', () => {
|
|||
it('makes a request to the bookmark', () => {
|
||||
subject()
|
||||
sinon.assert.calledWith(apiSource.apiFetch, bookmark, {
|
||||
Authorization: 'Bearer theJWT'
|
||||
Authorization: 'Bearer theJWT',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -413,7 +409,7 @@ describe('sources/api', () => {
|
|||
return apiSource
|
||||
.fetchIconMakerFolder({
|
||||
contextType: 'course',
|
||||
contextId: '22'
|
||||
contextId: '22',
|
||||
})
|
||||
.then(() => {
|
||||
sinon.assert.calledWith(
|
||||
|
@ -439,7 +435,7 @@ describe('sources/api', () => {
|
|||
return apiSource
|
||||
.fetchMediaFolder({
|
||||
contextType: 'course',
|
||||
contextId: '22'
|
||||
contextId: '22',
|
||||
})
|
||||
.then(() => {
|
||||
sinon.assert.calledWith(
|
||||
|
@ -522,7 +518,7 @@ describe('sources/api', () => {
|
|||
.then(() => {
|
||||
sinon.assert.calledWith(alertFuncSpy, {
|
||||
text: 'Something went wrong uploading, check your connection and try again.',
|
||||
variant: 'error'
|
||||
variant: 'error',
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
|
@ -552,7 +548,7 @@ describe('sources/api', () => {
|
|||
.then(() => {
|
||||
sinon.assert.calledWith(alertFuncSpy, {
|
||||
text: 'File storage quota exceeded',
|
||||
variant: 'error'
|
||||
variant: 'error',
|
||||
})
|
||||
})
|
||||
.catch(e => {})
|
||||
|
@ -569,7 +565,7 @@ describe('sources/api', () => {
|
|||
uploadUrl = 'upload-url'
|
||||
preflightProps = {
|
||||
upload_params: {},
|
||||
upload_url: uploadUrl
|
||||
upload_url: uploadUrl,
|
||||
}
|
||||
file = {url: 'file-url'}
|
||||
fetchMock.mock(uploadUrl, file)
|
||||
|
@ -586,7 +582,7 @@ describe('sources/api', () => {
|
|||
.then(() => {
|
||||
sinon.assert.calledWith(alertFuncSpy, {
|
||||
text: 'Something went wrong uploading, check your connection and try again.',
|
||||
variant: 'error'
|
||||
variant: 'error',
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
|
@ -646,7 +642,7 @@ describe('sources/api', () => {
|
|||
const fileId = '123'
|
||||
const response = {
|
||||
location: `http://canvas/api/v1/files/${fileId}?foo=bar`,
|
||||
uuid: 'xyzzy'
|
||||
uuid: 'xyzzy',
|
||||
}
|
||||
fetchMock.mock(preflightProps.upload_url, response)
|
||||
return apiSource.uploadFRD(fileDomObject, preflightProps).then(response => {
|
||||
|
@ -661,7 +657,7 @@ describe('sources/api', () => {
|
|||
const fileId = '1023~789'
|
||||
const response = {
|
||||
location: `http://canvas/api/v1/files/${fileId}?foo=bar`,
|
||||
uuid: 'xyzzy'
|
||||
uuid: 'xyzzy',
|
||||
}
|
||||
fetchMock.mock(preflightProps.upload_url, response)
|
||||
return apiSource.uploadFRD(fileDomObject, preflightProps).then(response => {
|
||||
|
@ -676,15 +672,15 @@ describe('sources/api', () => {
|
|||
describe('api mapping', () => {
|
||||
const body = {
|
||||
bookmark: 'mo.images',
|
||||
files: [{href: '/some/where', uuid: 'xyzzy'}]
|
||||
files: [{href: '/some/where', uuid: 'xyzzy'}],
|
||||
}
|
||||
props.images = {
|
||||
group: {
|
||||
isLoading: false,
|
||||
hasMore: true,
|
||||
bookmark: null,
|
||||
files: []
|
||||
}
|
||||
files: [],
|
||||
},
|
||||
}
|
||||
props.searchString = 'panda'
|
||||
|
||||
|
@ -702,7 +698,7 @@ describe('sources/api', () => {
|
|||
assert.deepStrictEqual(page, {
|
||||
bookmark: 'mo.images',
|
||||
files: [{href: '/some/where?wrap=1', uuid: 'xyzzy'}],
|
||||
searchString: 'panda'
|
||||
searchString: 'panda',
|
||||
})
|
||||
fetchMock.restore()
|
||||
})
|
||||
|
@ -716,7 +712,7 @@ describe('sources/api', () => {
|
|||
assert.deepEqual(page, {
|
||||
bookmark: 'mo.images',
|
||||
files: [{href: '/some/where?wrap=1', uuid: 'xyzzy'}],
|
||||
searchString: 'panda'
|
||||
searchString: 'panda',
|
||||
})
|
||||
fetchMock.restore()
|
||||
})
|
||||
|
@ -753,7 +749,7 @@ describe('sources/api', () => {
|
|||
const postBody = JSON.parse(fetchMock.lastOptions(uri).body)
|
||||
assert.deepEqual(postBody, {
|
||||
fileId,
|
||||
usageRight: usageRights.usageRight
|
||||
usageRight: usageRights.usageRight,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -863,7 +859,7 @@ describe('sources/api', () => {
|
|||
describe('headerFor', () => {
|
||||
it('returns an authorization header', () => {
|
||||
assert.deepStrictEqual(headerFor('the_jwt'), {
|
||||
Authorization: 'Bearer the_jwt'
|
||||
Authorization: 'Bearer the_jwt',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -880,8 +876,8 @@ describe('sources/api', () => {
|
|||
it('uses the windowOverride protocol if present', () => {
|
||||
const win = {
|
||||
location: {
|
||||
protocol: 'https:'
|
||||
}
|
||||
protocol: 'https:',
|
||||
},
|
||||
}
|
||||
assert.strictEqual(
|
||||
originFromHost('http://host:port', win),
|
||||
|
|
|
@ -839,6 +839,12 @@ describe "Files API", type: :request do
|
|||
it { is_expected.not_to include uncategorized }
|
||||
end
|
||||
|
||||
it "returns file category with the response" do
|
||||
json = api_call(:get, @files_path, @files_path_options, {})
|
||||
res = json.map { |f| f["category"] }
|
||||
expect(res).to eq %w[uncategorized uncategorized uncategorized]
|
||||
end
|
||||
|
||||
describe "sort" do
|
||||
it "lists files in alphabetical order" do
|
||||
json = api_call(:get, @files_path, @files_path_options, {})
|
||||
|
|
Loading…
Reference in New Issue