Skip to content

Commit b41982b

Browse files
eschiebelFelix Milea-Ciobanu
authored andcommitted
Add Enable Notification UI to sidebar and unsynched changes
The enable notificaiton checkboxes + message UI goes into the blueprint course sidebar and the uncynched changes modal. closes MC-149 test plan: - create a master course, add some content, open sidebar and sync > expect sidebar to show Associations and Sync History only - make some changes to the course content - open the sidebar > expect the sidebar to include Unsynched Changes section and Sync > button - check the "Enable Notifications" checkbox > expect the "Add a message" checkbox to appear - click on Unsynched Changes > expect modal to open showing your changes, the checked enable > notifications checkbox and the unchecked add a message checkbox - check add a message > expect a text box to appear - add some text - close the modal > expect the sidebar to open with the checkboxes checked and your text - click the sync button > expect it to spin, the unsynched changes + notificaation UI to be > replaced with a message > when syncing completes, the message disappears and you are left with > just assicoations and sync history - make another change - open the sidebar > expect the unsynched changes link to be back - click Unsynched Changes > expect the modal to open - click the Sync button in the footer > expect the modal to close and the side bar to look like it did when > you clicked the sync button earlier Change-Id: Ifd18c0c71346c2eefec007fcdeafbd287b096984 Reviewed-on: https://gerrit.instructure.com/110670 Tested-by: Jenkins Reviewed-by: Felix Milea-Ciobanu <fmileaciobanu@instructure.com> QA-Review: Heath Hales <hhales@instructure.com> Product-Review: Kendall Chadwick <kchadwick@instructure.com>
1 parent 8a2a12f commit b41982b

10 files changed

Lines changed: 259 additions & 59 deletions

File tree

app/jsx/blueprint_course_settings/actions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const types = [
3939
'REMOVE_COURSE_ASSOCIATIONS', 'UNDO_REMOVE_COURSE_ASSOCIATIONS',
4040
'CLEAR_ASSOCIATIONS',
4141
'LOAD_UNSYNCHED_CHANGES_START', 'LOAD_UNSYNCHED_CHANGES_SUCCESS', 'LOAD_UNSYNCHED_CHANGES_FAIL',
42-
'ENABLE_SEND_NOTIFICATION'
42+
'ENABLE_SEND_NOTIFICATION', 'INCLUDE_CUSTOM_NOTIFICATION_MESSAGE', 'SET_NOTIFICATION_MESSAGE'
4343
]
4444
const actions = createActions(...types)
4545

app/jsx/blueprint_course_settings/apiClient.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,14 @@ const ApiClient = {
7777
return axios.get(`/api/v1/courses/${course.id}/blueprint_templates/default/migrations`)
7878
},
7979

80-
beginMigration ({ course }) {
81-
return axios.post(`/api/v1/courses/${course.id}/blueprint_templates/default/migrations`)
80+
beginMigration ({ course, willSendNotification, willIncludeCustomNotificationMessage, notificationMessage}) {
81+
const params = {
82+
send_notification: willSendNotification
83+
}
84+
if (willIncludeCustomNotificationMessage && notificationMessage) {
85+
params.comment = notificationMessage
86+
}
87+
return axios.post(`/api/v1/courses/${course.id}/blueprint_templates/default/migrations`, params)
8288
},
8389

8490
checkMigration (state) {

app/jsx/blueprint_course_settings/components/CourseSidebar.js

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import select from 'jsx/shared/select'
2424

2525
import Button from 'instructure-ui/lib/components/Button'
2626
import Typography from 'instructure-ui/lib/components/Typography'
27-
import Checkbox from 'instructure-ui/lib/components/Checkbox'
2827
import Spinner from 'instructure-ui/lib/components/Spinner'
2928

3029
import propTypes from '../propTypes'
@@ -36,6 +35,7 @@ import BlueprintModal from './BlueprintModal'
3635
import { ConnectedMigrationSync as MigrationSync } from './MigrationSync'
3736
import { ConnectedBlueprintAssociations as BlueprintAssociations } from './BlueprintAssociations'
3837
import { ConnectedSyncHistory as SyncHistory } from './SyncHistory'
38+
import { ConnectedEnableNotification as EnableNotification } from './EnableNotification'
3939

4040
let UnsynchedChanges = null
4141

@@ -54,8 +54,10 @@ export default class CourseSidebar extends Component {
5454
isLoadingUnsynchedChanges: PropTypes.bool.isRequired,
5555
hasLoadedUnsynchedChanges: PropTypes.bool.isRequired,
5656
unsynchedChanges: propTypes.unsynchedChanges,
57+
isLoadingBeginMigration: PropTypes.bool.isRequired,
5758
migrationStatus: PropTypes.oneOf(MigrationStates.states)
5859
}
60+
5961
static defaultProps = {
6062
unsynchedChanges: [],
6163
migrationStatus: MigrationStates.unknown,
@@ -126,7 +128,11 @@ export default class CourseSidebar extends Component {
126128
doneButton: <MigrationSync
127129
showProgress={false}
128130
onClick={() => this.closeModal(() => {
129-
this.unsynchedChangesBtn.focus()
131+
if (this.unsynchedChangesBtn) {
132+
this.unsynchedChangesBtn.focus()
133+
} else {
134+
this.syncHistoryBtn.focus()
135+
}
130136
})}
131137
/>
132138
},
@@ -175,16 +181,27 @@ export default class CourseSidebar extends Component {
175181
// if we have unsynched changes, show the sync button
176182
maybeRenderSyncButton () {
177183
if (this.props.hasLoadedUnsynchedChanges && this.props.unsynchedChanges.length > 0) {
178-
return <MigrationSync />
184+
return (
185+
<div className="bcs__row bcs__row-sync-holder">
186+
<MigrationSync />
187+
</div>
188+
)
179189
}
180190
return null
181191
}
182192

183193
// if we have unsynched changes, show the button
184194
maybeRenderUnsynchedChanges () {
195+
// if loading changes, show spinner
185196
if (!this.props.hasLoadedUnsynchedChanges || this.props.isLoadingUnsynchedChanges) {
186197
return this.renderSpinner(I18n.t('Loading Unsynched Changes'))
187198
}
199+
// if syncing, hide
200+
const isSyncing = MigrationStates.isLoadingState(this.props.migrationStatus) || this.props.isLoadingBeginMigration
201+
if (isSyncing) {
202+
return null
203+
}
204+
// if changes are loaded, show me
188205
if (this.props.hasLoadedUnsynchedChanges && this.props.unsynchedChanges.length > 0) {
189206
return (
190207
<div className="bcs__row">
@@ -197,13 +214,7 @@ export default class CourseSidebar extends Component {
197214
<Typography>{I18n.t('Unsynched Changes')}</Typography>
198215
</Button>
199216
<Typography><span className="bcs__row-right-content">{this.props.unsynchedChanges.length}</span></Typography>
200-
<div className="bcs__history-notification">
201-
<Checkbox
202-
label={I18n.t('Send Notification')}
203-
checked={this.props.willSendNotification}
204-
onChange={this.handleSendNotificationClick}
205-
/>
206-
</div>
217+
<EnableNotification />
207218
</div>
208219
)
209220
}
@@ -238,7 +249,7 @@ export default class CourseSidebar extends Component {
238249
<Typography><span className="bcs__row-right-content">{this.props.associations.length}</span></Typography>
239250
</div>
240251
<div className="bcs__row">
241-
<Button is="mcSyncHistoryBtn" ref={(c) => { this.syncHistoryBtn = c }} variant="link" onClick={this.handleSyncHistoryClick}>
252+
<Button id="mcSyncHistoryBtn" ref={(c) => { this.syncHistoryBtn = c }} variant="link" onClick={this.handleSyncHistoryClick}>
242253
<Typography>{I18n.t('Sync History')}</Typography>
243254
</Button>
244255
</div>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import React from 'react'
2+
import I18n from 'i18n!blueprint_settings'
3+
import { connect } from 'react-redux'
4+
import { bindActionCreators } from 'redux'
5+
import select from 'jsx/shared/select'
6+
import $ from 'jquery'
7+
8+
import Checkbox from 'instructure-ui/lib/components/Checkbox'
9+
import TextArea from 'instructure-ui/lib/components/TextArea'
10+
import Typography from 'instructure-ui/lib/components/Typography'
11+
import ScreenReaderContent from 'instructure-ui/lib/components/ScreenReaderContent'
12+
13+
import actions from '../actions'
14+
import MigrationStates from '../migrationStates'
15+
16+
const MAX_NOTIFICATION_MESSAGE_LENGTH = 140
17+
18+
export default class EnableNotification extends React.Component {
19+
static propTypes = {
20+
migrationStatus: React.PropTypes.oneOf(MigrationStates.states).isRequired,
21+
willSendNotification: React.PropTypes.bool.isRequired,
22+
willIncludeCustomNotificationMessage: React.PropTypes.bool.isRequired,
23+
notificationMessage: React.PropTypes.string.isRequired,
24+
enableSendNotification: React.PropTypes.func.isRequired,
25+
includeCustomNotificationMessage: React.PropTypes.func.isRequired,
26+
setNotificationMessage: React.PropTypes.func.isRequired
27+
}
28+
29+
handleSendNotificationChange = (event) => {
30+
this.props.enableSendNotification(event.target.checked)
31+
}
32+
33+
handleAddAMessageChange = (event) => {
34+
this.props.includeCustomNotificationMessage(event.target.checked)
35+
}
36+
37+
handleChangeMessage = (event) => {
38+
const msg = event.target.value.slice(0, MAX_NOTIFICATION_MESSAGE_LENGTH);
39+
if (msg.length === MAX_NOTIFICATION_MESSAGE_LENGTH) {
40+
$.screenReaderFlashMessage(
41+
I18n.t('You have reached the limit of %{len} characters in the notification message', {len: MAX_NOTIFICATION_MESSAGE_LENGTH})
42+
)
43+
}
44+
this.props.setNotificationMessage(msg)
45+
}
46+
47+
render () {
48+
const isDisabled = MigrationStates.isLoadingState(this.props.migrationStatus)
49+
return (
50+
<div className="bcs__history-notification">
51+
<div className="bcs__history-notification__enable">
52+
<Checkbox
53+
label={I18n.t('Send Notification')}
54+
checked={this.props.willSendNotification}
55+
onChange={this.handleSendNotificationChange}
56+
size="small"
57+
disabled={isDisabled}
58+
/>
59+
</div>
60+
{this.props.willSendNotification ?
61+
<div className="bcs__history-notification__add-message">
62+
<Checkbox
63+
label={I18n.t('Add a Message')}
64+
checked={this.props.willIncludeCustomNotificationMessage}
65+
onChange={this.handleAddAMessageChange}
66+
isBlock={false}
67+
size="small"
68+
disabled={isDisabled}
69+
/>
70+
<Typography as="span" color="secondary" size="small">({this.props.notificationMessage.length}/140)</Typography>
71+
</div> : null
72+
}
73+
{this.props.willSendNotification && this.props.willIncludeCustomNotificationMessage ?
74+
<div className="bcs__history-notification__message">
75+
<TextArea
76+
label={<ScreenReaderContent>{I18n.t('Message text')}</ScreenReaderContent>}
77+
autoGrow={false}
78+
resize="vertical"
79+
isBlock
80+
value={this.props.notificationMessage}
81+
onChange={this.handleChangeMessage}
82+
disabled={isDisabled}
83+
/>
84+
</div> : null
85+
}
86+
</div>
87+
)
88+
}
89+
}
90+
91+
const connectState = state =>
92+
select(state, [
93+
'migrationStatus',
94+
'willSendNotification',
95+
'willIncludeCustomNotificationMessage',
96+
'notificationMessage',
97+
])
98+
const connectActions = dispatch => bindActionCreators(actions, dispatch)
99+
export const ConnectedEnableNotification = connect(connectState, connectActions)(EnableNotification)

app/jsx/blueprint_course_settings/components/UnsynchedChanges.js

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,22 @@ import select from 'jsx/shared/select'
66

77
import Alert from 'instructure-ui/lib/components/Alert'
88
import Heading from 'instructure-ui/lib/components/Heading'
9-
import Checkbox from 'instructure-ui/lib/components/Checkbox'
109

1110
import UnsynchedChange from './UnsynchedChange'
11+
import { ConnectedEnableNotification as EnableNotification } from './EnableNotification'
1212

1313
import actions from '../actions'
1414
import propTypes from '../propTypes'
1515

16-
const { func, bool } = React.PropTypes
17-
1816
export default class UnsynchedChanges extends Component {
1917
static propTypes = {
2018
unsynchedChanges: propTypes.unsynchedChanges,
21-
willSendNotification: bool.isRequired,
22-
enableSendNotification: func.isRequired,
2319
}
2420

2521
static defaultProps = {
2622
unsynchedChanges: [],
2723
}
2824

29-
handleSendNotificationClick = (event) => {
30-
const enabled = event.target.checked
31-
this.props.enableSendNotification(enabled)
32-
}
33-
3425
maybeRenderChanges () {
3526
return (
3627
this.props.unsynchedChanges.length === 0
@@ -52,15 +43,7 @@ export default class UnsynchedChanges extends Component {
5243
{this.props.unsynchedChanges.map(change =>
5344
(<UnsynchedChange key={change.asset_id} change={change} />)
5445
)}
55-
<div className="bcs__history-notification">
56-
<div>
57-
<Checkbox
58-
label={I18n.t('Send Notification')}
59-
checked={this.props.willSendNotification}
60-
onChange={this.handleSendNotificationClick}
61-
/>
62-
</div>
63-
</div>
46+
<EnableNotification />
6447
</div>
6548
)
6649
}
@@ -76,10 +59,7 @@ export default class UnsynchedChanges extends Component {
7659

7760
const connectState = state =>
7861
select(state, [
79-
'hasLoadedUnsynchedChanges',
80-
'isLoadingUnsynchedChanges',
8162
'unsynchedChanges',
82-
'willSendNotification',
8363
])
8464
const connectActions = dispatch => bindActionCreators(actions, dispatch)
8565
export const ConnectedUnsynchedChanges = connect(connectState, connectActions)(UnsynchedChanges)

app/jsx/blueprint_course_settings/reducer.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ export default combineReducers({
118118
willSendNotification: handleActions({
119119
[actionTypes.ENABLE_SEND_NOTIFICATION]: (state, action) => action.payload
120120
}, false),
121+
willIncludeCustomNotificationMessage: handleActions({
122+
[actionTypes.INCLUDE_CUSTOM_NOTIFICATION_MESSAGE]: (state, action) => action.payload
123+
}, false),
124+
notificationMessage: handleActions({
125+
[actionTypes.SET_NOTIFICATION_MESSAGE]: (state, action) => action.payload
126+
}, ''),
121127
errors: (state = [], action) => (
122128
action.error
123129
? state.concat([action.payload.message])

app/stylesheets/bundles/blueprint_course_settings.scss

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
.bcs__content {
2727
width: 270px;
28+
border-bottom: 2px solid $ic-border-color;
2829

2930
.bcs__body {
3031
margin: 0 1.5em;
@@ -306,19 +307,36 @@
306307
}
307308
}
308309

309-
.bcs__body .bcs__migration-sync .bcs__migration-sync__button { // when sync button is in the sidebar
310-
float: right;
311-
}
312-
313310
.bcs__history-notification {
314311
margin-bottom: 1em;
315312
}
316313

314+
.bcs__migration-sync__button {
315+
display: flex;
316+
justify-content: flex-end;
317+
}
318+
319+
.bcs__row.bcs__row-sync-holder {
320+
padding: 1em 0
321+
}
317322

318323
.bcs__migration-sync .bcs__migration-sync__loading {
319324
margin-bottom: .5em;
320325
}
321326

327+
/* EnableNotification component styles */
328+
.bcs__history-notification__enable,
329+
.bcs__history-notification__add-message,
330+
.bcs__history-notification__message {
331+
margin-bottom:.5em;
332+
}
333+
.bcs__history-notification__add-message {
334+
margin-left: 1em;
335+
label {
336+
margin-right: .5em;
337+
}
338+
}
339+
322340
/* unsynched changes are a little different than sync history */
323341
.bcs__history-item__content.bcs__unsynched-change__content {
324342
.bcs__history-item__uncynched-changes-grid {
@@ -331,7 +349,4 @@
331349

332350
.bcs__unsynched-changes .bcs__history-notification {
333351
padding-top: 1em;
334-
display: flex;
335-
flex-direction: row;
336-
justify-content: flex-end;
337352
}

spec/javascripts/jsx/blueprint_course_settings/components/CourseSidebarSpec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ QUnit.module('CourseSidebar component')
2828
test('renders the CourseSidebar component', () => {
2929
const tree = enzyme.shallow(<CourseSidebar {...defaultProps()} />)
3030
const rows = tree.find('.bcs__row')
31-
equal(rows.length, 3)
31+
equal(rows.length, 4)
3232
})

0 commit comments

Comments
 (0)