Add new Gradebook filters button and tray

This is groundwork for subsequent filters work.

Test plan:

- Enable enhanced gradebook filters
 - "Applied Filters: None" should show above grid
 - Filters button should show above grid
  - Click button
  - Tray should expand
- Bonus: See FilterNav in Storybook

flag=enhanced_gradebook_filters

Closes EVAL-1891

Change-Id: I45e44415bcd39b420a962cb79face12b7d6eef54
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/275899
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Kai Bjorkman <kbjorkman@instructure.com>
Product-Review: Syed Hussain <shussain@instructure.com>
Reviewed-by: Spencer Olson <solson@instructure.com>
Reviewed-by: Adrian Packel <apackel@instructure.com>
This commit is contained in:
Aaron Shafovaloff 2021-10-11 11:32:01 -05:00
parent 884495090a
commit d02369cef3
7 changed files with 200 additions and 0 deletions

View File

@ -106,6 +106,7 @@
<div id="gradebook-assignment-search" style="flex: 1;"></div>
</div>
<% end %>
<div id="gradebook-filter-nav"></div>
<div id="gradebook-grid-wrapper" class="use-css-transitions-for-show-hide" style="display:none;">
<div id="gradebook_grid"></div>
</div>

View File

@ -27,6 +27,8 @@ const icons = {
IconBulletListSquareLine: '',
IconClearTextFormattingLine: '',
IconDocumentLine: '',
IconFilterLine: '',
IconFilterSolid: '',
IconFullScreenLine: '',
IconImageLine: '',
IconIndentLine: '',
@ -36,6 +38,8 @@ const icons = {
IconNumberedListLine: '',
IconOutdentLine: '',
IconRemoveLinkLine: '',
IconSettingsLine: '',
IconSettingsSolid: '',
IconStrikethroughLine: '',
IconTextCenteredLine: '',
IconTextEndLine: '',

View File

@ -23,6 +23,7 @@ import Gradebook from './react/default_gradebook/Gradebook'
import('@canvas/context-cards/react/StudentContextCardTrigger')
const mountPoint = document.querySelector('#gradebook_app')
const filterNavNode = document.querySelector('#gradebook-filter-nav')
const gradebookMenuNode = document.querySelector('[data-component="GradebookMenu"]')
const settingsModalButtonContainer = document.getElementById(
'gradebook-settings-modal-button-container'
@ -34,6 +35,7 @@ const props = {
locale: ENV.LOCALE,
gradebookMenuNode,
gridColorNode,
filterNavNode,
settingsModalButtonContainer,
gradebookEnv: ENV.GRADEBOOK_OPTIONS
}

View File

@ -55,6 +55,7 @@ import PostPolicies from './PostPolicies/index'
import GradebookMenu from '@canvas/gradebook-menu'
import ViewOptionsMenu from './components/ViewOptionsMenu'
import ActionMenu from './components/ActionMenu'
import FilterNav from './components/FilterNav'
import EnhancedActionMenu from './components/EnhancedActionMenu'
import AssignmentGroupFilter from './components/content-filters/AssignmentGroupFilter'
import GradingPeriodFilter from './components/content-filters/GradingPeriodFilter'
@ -4330,6 +4331,11 @@ class Gradebook extends React.Component {
<Portal node={this.props.gridColorNode}>
<GridColor colors={this.state.gridColors} />
</Portal>
{this.options.enhanced_gradebook_filters && (
<Portal node={this.props.filterNavNode}>
<FilterNav />
</Portal>
)}
</>
)
}

View File

@ -0,0 +1,115 @@
/*
* Copyright (C) 2021 - 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 React, {useState} from 'react'
import {Button, CloseButton} from '@instructure/ui-buttons'
import {AccessibleContent} from '@instructure/ui-a11y-content'
import I18n from 'i18n!gradebook'
import {IconFilterSolid, IconFilterLine} from '@instructure/ui-icons'
import {View} from '@instructure/ui-view'
import {Flex} from '@instructure/ui-flex'
import {Tag} from '@instructure/ui-tag'
import {Tray} from '@instructure/ui-tray'
import {Text} from '@instructure/ui-text'
import {Heading} from '@instructure/ui-heading'
export default function FilterNav() {
const [isTrayOpen, setIsTrayOpen] = useState(false)
const openTray = () => {
setIsTrayOpen(true)
}
// empty for now
const filters = [].map(({label}) => {
return (
<Tag
text={<AccessibleContent alt={I18n.t('Remove filter')}>{label}</AccessibleContent>}
dismissible
onClick={() => {}}
margin="0 xx-small 0 0"
/>
)
})
return (
<Flex justifyItems="space-between" padding="0 0 small 0">
<Flex.Item>
<Flex>
<Flex.Item padding="0 x-small 0 0">
<IconFilterLine /> <Text weight="bold">{I18n.t('Applied Filters:')}</Text>
</Flex.Item>
<Flex.Item>
{filters.length ? (
filters
) : (
<Text color="secondary" weight="bold">
{I18n.t('None')}
</Text>
)}
</Flex.Item>
</Flex>
</Flex.Item>
<Flex.Item>
<Button
renderIcon={IconFilterSolid}
id="gradebook-settings-button"
color="secondary"
onClick={openTray}
>
{I18n.t('Filters')}
</Button>
</Flex.Item>
<Tray
placement="end"
label="Tray Example"
open={isTrayOpen}
onDismiss={() => setIsTrayOpen(false)}
size="small"
>
<View as="div" padding="medium">
<Flex>
<Flex.Item shouldGrow shouldShrink>
<Heading level="h3" as="h3" margin="0 0 x-small">
{I18n.t('Gradebook Filters')}
</Heading>
</Flex.Item>
<Flex.Item>
<CloseButton
placement="end"
offset="small"
screenReaderLabel="Close"
onClick={() => setIsTrayOpen(false)}
/>
</Flex.Item>
</Flex>
<Button
renderIcon={IconFilterLine}
color="secondary"
onClick={() => {}}
margin="small 0 0 0"
withVisualDebug
data-testid="new-filter-button"
>
{I18n.t('Create New Filter')}
</Button>
</View>
</Tray>
</Flex>
)
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2021 - 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 React from 'react'
import FilterNav from './FilterNav'
export default {
title: 'Examples/Evaluate/Gradebook/FilterNav',
component: FilterNav,
args: {}
}
const Template = args => <FilterNav {...args} />
export const Default = Template.bind({})

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2021 - 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 React from 'react'
import {Default as FilterNav} from '../FilterNav.stories'
import {render, fireEvent, within, cleanup, screen} from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
describe('FilterNav', () => {
it('renders filters button', () => {
const {getByRole} = render(<FilterNav {...FilterNav.args} />)
expect(getByRole('button', {name: 'Filters'})).toBeInTheDocument()
})
it('opens tray', () => {
const {container} = render(<FilterNav {...FilterNav.args} />)
fireEvent.click(within(container).getByText('Filters'))
expect(screen.getByRole('heading')).toHaveTextContent('Gradebook Filters')
cleanup()
})
it('renders new filter button', () => {
const {container} = render(<FilterNav {...FilterNav.args} />)
fireEvent.click(within(container).getByText('Filters'))
expect(screen.getByRole('button', {name: /Create New Filter/})).toBeInTheDocument()
cleanup()
})
})