123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- /*
- * Copyright (C) 2017 - 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 $ from 'jquery'
- import React from 'react'
- import PropTypes from 'prop-types'
- import IconMiniArrowDownSolid from 'instructure-icons/lib/Solid/IconMiniArrowDownSolid'
- import Button from 'instructure-ui/lib/components/Button'
- import { MenuItem, MenuItemSeparator } from 'instructure-ui/lib/components/Menu'
- import PopoverMenu from 'instructure-ui/lib/components/PopoverMenu'
- import Typography from 'instructure-ui/lib/components/Typography'
- import GradebookExportManager from 'jsx/gradezilla/shared/GradebookExportManager'
- import { AppLaunch } from 'jsx/gradezilla/SISGradePassback/PostGradesApp'
- import tz from 'timezone'
- import DateHelper from 'jsx/shared/helpers/dateHelper'
- import I18n from 'i18n!gradebook'
- import 'compiled/jquery.rails_flash_notifications'
- const { arrayOf, bool, func, object, shape, string } = PropTypes;
- class ActionMenu extends React.Component {
- static defaultProps = {
- lastExport: undefined,
- attachment: undefined,
- postGradesLtis: [],
- publishGradesToSis: {
- publishToSisUrl: undefined
- }
- };
- static propTypes = {
- gradebookIsEditable: bool.isRequired,
- contextAllowsGradebookUploads: bool.isRequired,
- gradebookImportUrl: string.isRequired,
- currentUserId: string.isRequired,
- gradebookExportUrl: string.isRequired,
- lastExport: shape({
- progressId: string.isRequired,
- workflowState: string.isRequired,
- }),
- attachment: shape({
- id: string.isRequired,
- downloadUrl: string.isRequired,
- updatedAt: string.isRequired
- }),
- postGradesLtis: arrayOf(shape({
- id: string.isRequired,
- name: string.isRequired,
- onSelect: func.isRequired
- })),
- postGradesFeature: shape({
- enabled: bool.isRequired,
- store: object.isRequired,
- returnFocusTo: object
- }).isRequired,
- publishGradesToSis: shape({
- isEnabled: bool.isRequired,
- publishToSisUrl: string
- })
- };
- static downloadableLink (url) {
- return `${url}&download_frd=1`;
- }
- static gotoUrl (url) {
- window.location.href = url;
- }
- static initialState = {
- exportInProgress: false
- };
- constructor (props) {
- super(props);
- this.state = ActionMenu.initialState;
- this.launchPostGrades = this.launchPostGrades.bind(this);
- }
- componentWillMount () {
- const existingExport = this.getExistingExport();
- this.exportManager = new GradebookExportManager(this.props.gradebookExportUrl, this.props.currentUserId, existingExport);
- }
- componentWillUnmount () {
- if (this.exportManager) this.exportManager.clearMonitor();
- }
- getExistingExport () {
- if (!(this.props.lastExport && this.props.attachment)) return undefined;
- if (!(this.props.lastExport.progressId && this.props.attachment.id)) return undefined;
- return {
- progressId: this.props.lastExport.progressId,
- attachmentId: this.props.attachment.id,
- workflowState: this.props.lastExport.workflowState
- };
- }
- setExportInProgress (status) {
- this.setState({ exportInProgress: !!status });
- }
- handleExport () {
- this.setExportInProgress(true);
- $.flashMessage(I18n.t('Gradebook export started'));
- return this.exportManager.startExport().then((resolution) => {
- this.setExportInProgress(false);
- const attachmentUrl = resolution.attachmentUrl;
- const updatedAt = new Date(resolution.updatedAt);
- const previousExport = {
- label: `${I18n.t('New Export')} (${DateHelper.formatDatetimeForDisplay(updatedAt)})`,
- attachmentUrl: ActionMenu.downloadableLink(attachmentUrl)
- };
- this.setState({ previousExport });
- // Since we're still on the page, let's automatically download the CSV for them as well
- ActionMenu.gotoUrl(attachmentUrl);
- }).catch((reason) => {
- this.setExportInProgress(false);
- $.flashError(I18n.t('Gradebook Export Failed: %{reason}', { reason }));
- });
- }
- handleImport () {
- ActionMenu.gotoUrl(this.props.gradebookImportUrl);
- }
- handlePublishGradesToSis () {
- ActionMenu.gotoUrl(this.props.publishGradesToSis.publishToSisUrl);
- }
- disableImports () {
- return !(this.props.gradebookIsEditable && this.props.contextAllowsGradebookUploads);
- }
- lastExportFromProps () {
- if (!(this.props.lastExport && this.props.lastExport.workflowState === 'completed')) return undefined;
- return this.props.lastExport;
- }
- lastExportFromState () {
- if (this.state.exportInProgress || !this.state.previousExport) return undefined;
- return this.state.previousExport;
- }
- previousExport () {
- const completedExportFromState = this.lastExportFromState();
- if (completedExportFromState) return completedExportFromState;
- const completedLastExport = this.lastExportFromProps();
- const attachment = completedLastExport && this.props.attachment;
- if (!completedLastExport || !attachment) return undefined;
- const updatedAt = tz.parse(attachment.updatedAt);
- return {
- label: `${I18n.t('Previous Export')} (${DateHelper.formatDatetimeForDisplay(updatedAt)})`,
- attachmentUrl: ActionMenu.downloadableLink(attachment.downloadUrl)
- };
- }
- exportInProgress () {
- return this.state.exportInProgress;
- }
- launchPostGrades () {
- const { store, returnFocusTo } = this.props.postGradesFeature;
- setTimeout(() => AppLaunch(store, returnFocusTo), 10);
- }
- renderPostGradesTools () {
- const tools = this.renderPostGradesLtis();
- if (this.props.postGradesFeature.enabled) {
- tools.push(this.renderPostGradesFeature());
- }
- if (tools.length) {
- tools.push(<MenuItemSeparator key="postGradesSeparator" />);
- }
- return tools;
- }
- renderPostGradesLtis () {
- return this.props.postGradesLtis.map((tool) => {
- const key = `post_grades_lti_${tool.id}`;
- return (
- <MenuItem onSelect={tool.onSelect} key={key}>
- <span data-menu-id={key}>
- {I18n.t('Sync to %{name}', {name: tool.name})}
- </span>
- </MenuItem>
- );
- });
- }
- renderPostGradesFeature () {
- const sisName = this.props.postGradesFeature.label || I18n.t('SIS');
- return (
- <MenuItem onSelect={this.launchPostGrades} key="post_grades_feature_tool">
- <span data-menu-id="post_grades_feature_tool">
- {I18n.t('Sync to %{sisName}', {sisName})}
- </span>
- </MenuItem>
- );
- }
- renderPreviousExports () {
- const previousExport = this.previousExport();
- if (!previousExport) return '';
- const lastExportDescription = previousExport.label;
- const downloadFrdUrl = previousExport.attachmentUrl;
- const previousMenu = (
- <MenuItem key="previousExport" onSelect={() => { ActionMenu.gotoUrl(downloadFrdUrl) }}>
- <span data-menu-id="previous-export">{lastExportDescription}</span>
- </MenuItem>
- );
- return [
- (<MenuItemSeparator key="previousExportSeparator" />),
- previousMenu
- ];
- }
- renderPublishGradesToSis () {
- const { isEnabled, publishToSisUrl } = this.props.publishGradesToSis;
- if (!isEnabled || !publishToSisUrl) {
- return null;
- }
- return (
- <MenuItem onSelect={() => { this.handlePublishGradesToSis() }}>
- <span data-menu-id="publish-grades-to-sis">
- {I18n.t('Sync grades to SIS')}
- </span>
- </MenuItem>
- );
- }
- render () {
- const buttonTypographyProps = {
- weight: 'normal',
- style: 'normal',
- size: 'medium',
- color: 'primary'
- };
- const publishGradesToSis = this.renderPublishGradesToSis();
- return (
- <PopoverMenu
- trigger={
- <Button variant="link">
- <Typography {...buttonTypographyProps}>
- { I18n.t('Actions') }<IconMiniArrowDownSolid />
- </Typography>
- </Button>
- }
- >
- { this.renderPostGradesTools() }
- {publishGradesToSis}
- <MenuItem disabled={this.disableImports()} onSelect={() => { this.handleImport() }}>
- <span data-menu-id="import">{ I18n.t('Import') }</span>
- </MenuItem>
- <MenuItem disabled={this.exportInProgress()} onSelect={() => { this.handleExport() }}>
- <span data-menu-id="export">
- { this.exportInProgress() ? I18n.t('Export in progress') : I18n.t('Export') }
- </span>
- </MenuItem>
- { [...this.renderPreviousExports()] }
- </PopoverMenu>
- );
- }
- }
- export default ActionMenu
|