Add dev keys visible toggle

Refs: PLAT-3153

Test Plan:  With the feature flag enabled, you can toggle the visible
state on and off in the developer keys UI.

Change-Id: Ifa1b09f0cc667123638196ee11dc765e90514717
Reviewed-on: https://gerrit.instructure.com/143878
Reviewed-by: Andrew Butterfield <abutterfield@instructure.com>
Tested-by: Jenkins
QA-Review: August Thornton <august@instructure.com>
Product-Review: Stewie aka Nicholas Stewart <nstewart@instructure.com>
This commit is contained in:
Stewie (Nicholas Stewart) 2018-03-16 13:13:11 -06:00 committed by Stewie aka Nicholas Stewart
parent 336cd1af94
commit cc2f39ad36
8 changed files with 422 additions and 41 deletions

View File

@ -18,6 +18,7 @@
import classNames from 'classnames'
import $ from 'jquery'
import 'jquery.instructure_date_and_time'
import 'jqueryui/dialog'
import I18n from 'i18n!react_developer_keys'
import React from 'react'
@ -32,6 +33,8 @@ class DeveloperKey extends React.Component {
this.editLinkHandler = this.editLinkHandler.bind(this);
this.deleteLinkHandler = this.deleteLinkHandler.bind(this);
this.focusDeleteLink = this.focusDeleteLink.bind(this);
this.makeVisibleLinkHandler = this.makeVisibleLinkHandler.bind(this);
this.makeInvisibleLinkHandler = this.makeInvisibleLinkHandler.bind(this);
}
activateLinkHandler (event)
@ -54,6 +57,26 @@ class DeveloperKey extends React.Component {
)
}
makeVisibleLinkHandler (event)
{
event.preventDefault()
this.props.store.dispatch(
this.props.actions.makeVisibleDeveloperKey(
this.props.developerKey
)
)
}
makeInvisibleLinkHandler (event)
{
event.preventDefault()
this.props.store.dispatch(
this.props.actions.makeInvisibleDeveloperKey(
this.props.developerKey
)
)
}
deleteLinkHandler (event)
{
event.preventDefault()
@ -100,54 +123,109 @@ class DeveloperKey extends React.Component {
this.deleteLink.focus();
}
getLinkHelper(options) {
const iconClassName = `icon-${options.iconType} standalone-icon`
return (
<a href="#"
role={options.role}
aria-checked={options.ariaChecked} aria-label={options.ariaLabel}
className={options.className} title={options.title}
ref={options.refLink}
onClick={options.onClick}>
<i className={iconClassName} />
</a>)
}
getActivateLink(developerName) {
return this.getLinkHelper({
role: "checkbox",
ariaChecked: "false",
ariaLabel: I18n.t("Activate key %{developerName}", {developerName}),
className: "deactivate_link",
title: I18n.t("Activate this key"),
onClick: this.activateLinkHandler,
iconType: "unlock",
})
}
getDeactivateLink(developerName) {
return this.getLinkHelper({
role: "checkbox",
ariaChecked: "true",
ariaLabel: I18n.t("Deactivate key %{developerName}", {developerName}),
className: "deactivate_link",
title: I18n.t("Deactivate this key"),
onClick: this.deactivateLinkHandler,
iconType: "lock"
})
}
getEditLink(developerName) {
return this.getLinkHelper({
ariaChecked: null,
ariaLabel: I18n.t("Edit key %{developerName}", {developerName}),
className: "edit_link",
title: I18n.t("Edit this key"),
onClick: this.editLinkHandler,
iconType: "edit"
})
}
refDeleteLink = (link) => { this.deleteLink = link; }
getDeleteLink(developerName) {
return this.getLinkHelper({
ariaChecked: null,
ariaLabel: I18n.t("Delete key %{developerName}", {developerName}),
className: "delete_link",
title: I18n.t("Delete this key"),
onClick: this.deleteLinkHandler,
iconType: "trash",
refLink: this.refDeleteLink
})
}
getMakeVisibleLink() {
const label = I18n.t("Make key visible")
return this.getLinkHelper({
role: "checkbox",
ariaChecked: false,
ariaLabel: label,
className: "deactivate_link",
title: label,
onClick: this.makeVisibleLinkHandler,
iconType: "off",
})
}
getMakeInvisibleLink() {
const label = I18n.t("Make key invisible")
return this.getLinkHelper({
role: "checkbox",
ariaChecked: true,
ariaLabel: label,
className: "deactivate_link",
title: label,
onClick: this.makeInvisibleLinkHandler,
iconType: "eye",
})
}
links (developerKey) {
const developerNameCached = this.developerName(developerKey)
const localizedActivateLabel = I18n.t("Activate key %{developerName}", {developerName: developerNameCached})
const activateLink = (
<a href="#" role="checkbox"
aria-checked="false" aria-label={localizedActivateLabel}
className="deactivate_link" title={I18n.t("Activate this key")}
onClick={this.activateLinkHandler}>
<i className="icon-unlock standalone-icon" />
</a>)
const localizedDeactivateLabel = I18n.t("Deactivate key %{developerName}", {developerName: developerNameCached})
const deactivateLink = (
<a href="#" role="checkbox"
aria-checked="true" aria-label={localizedDeactivateLabel}
className="deactivate_link" title={I18n.t("Deactivate this key")}
onClick={this.deactivateLinkHandler}>
<i className="icon-lock standalone-icon" />
</a>)
const localizedEditLabel = I18n.t("Edit key %{developerName}", {developerName: developerNameCached})
const editLink = (
<a href="#" className="edit_link"
aria-label={localizedEditLabel}
title={I18n.t("Edit this key")}
onClick={this.editLinkHandler}>
<i className="icon-edit standalone-icon" />
</a>)
const localizedDeleteLabel = I18n.t("Delete key %{developerName}", {developerName: developerNameCached})
const deleteLink = (
<a href="#" className="delete_link"
aria-label={localizedDeleteLabel}
title={I18n.t("Delete this key")}
ref={(link) => { this.deleteLink = link; }}
onClick={this.deleteLinkHandler}>
<i className="icon-trash standalone-icon" />
</a>)
const activateLink = this.getActivateLink(developerNameCached);
const deactivateLink = this.getDeactivateLink(developerNameCached)
const editLink = this.getEditLink(developerNameCached)
const deleteLink = this.getDeleteLink(developerNameCached)
const makeVisibleLink = this.getMakeVisibleLink()
const makeInvisibleLink = this.getMakeInvisibleLink()
return (
<div>
{editLink}
{this.isActive(developerKey) ? deactivateLink : activateLink}
{developerKey.visible ? makeInvisibleLink : makeVisibleLink}
{deleteLink}
</div>
)
@ -245,6 +323,8 @@ DeveloperKey.propTypes = {
dispatch: PropTypes.func.isRequired,
}).isRequired,
actions: PropTypes.shape({
makeVisibleDeveloperKey: PropTypes.func.isRequired,
makeInvisibleDeveloperKey: PropTypes.func.isRequired,
activateDeveloperKey: PropTypes.func.isRequired,
deactivateDeveloperKey: PropTypes.func.isRequired,
deleteDeveloperKey: PropTypes.func.isRequired,

View File

@ -50,6 +50,24 @@ actions.deactivateDeveloperKeySuccessful = (payload) => ({ type: actions.DEACTIV
actions.DEACTIVATE_DEVELOPER_KEY_FAILED = 'DEACTIVATE_DEVELOPER_KEY_FAILED';
actions.deactivateDeveloperKeyFailed = (error) => ({ type: actions.DEACTIVATE_DEVELOPER_KEY_FAILED, error: true, payload: error });
actions.MAKE_VISIBLE_DEVELOPER_KEY_START = 'MAKE_VISIBLE_DEVELOPER_KEY_START';
actions.makeVisibleDeveloperKeyStart = () => ({ type: actions.MAKE_VISIBLE_DEVELOPER_KEY_START });
actions.MAKE_VISIBLE_DEVELOPER_KEY_SUCCESSFUL = 'MAKE_VISIBLE_DEVELOPER_KEY_SUCCESSFUL';
actions.makeVisibleDeveloperKeySuccessful = () => ({ type: actions.MAKE_VISIBLE_DEVELOPER_KEY_SUCCESSFUL });
actions.MAKE_VISIBLE_DEVELOPER_KEY_FAILED = 'MAKE_VISIBLE_DEVELOPER_KEY_FAILED';
actions.makeVisibleDeveloperKeyFailed = (error) => ({ type: actions.MAKE_VISIBLE_DEVELOPER_KEY_FAILED, error: true, payload: error });
actions.MAKE_INVISIBLE_DEVELOPER_KEY_START = 'MAKE_INVISIBLE_DEVELOPER_KEY_START';
actions.makeInvisibleDeveloperKeyStart = () => ({ type: actions.MAKE_INVISIBLE_DEVELOPER_KEY_START });
actions.MAKE_INVISIBLE_DEVELOPER_KEY_SUCCESSFUL = 'MAKE_INVISIBLE_DEVELOPER_KEY_SUCCESSFUL';
actions.makeInvisibleDeveloperKeySuccessful = () => ({ type: actions.MAKE_INVISIBLE_DEVELOPER_KEY_SUCCESSFUL });
actions.MAKE_INVISIBLE_DEVELOPER_KEY_FAILED = 'MAKE_INVISIBLE_DEVELOPER_KEY_FAILED';
actions.makeInvisibleDeveloperKeyFailed = (error) => ({ type: actions.MAKE_INVISIBLE_DEVELOPER_KEY_FAILED, error: true, payload: error });
actions.DELETE_DEVELOPER_KEY_START = 'DELETE_DEVELOPER_KEY_START';
actions.deleteDeveloperKeyStart = (payload) => ({ type: actions.DELETE_DEVELOPER_KEY_START, payload });
@ -174,6 +192,38 @@ actions.activateDeveloperKey = (developerKey) => (dispatch, _getState) => {
.catch((err) => dispatch(actions.activateDeveloperKeyFailed(err)));
};
actions.makeInvisibleDeveloperKey = (developerKey) => (dispatch, _getState) => {
dispatch(actions.makeInvisibleDeveloperKeyStart());
const url = `/api/v1/developer_keys/${developerKey.id}`
axios.put(url,
{
developer_key: {visible: false}
}
)
.then((response) => {
dispatch(actions.listDeveloperKeysReplace(response.data))
dispatch(actions.makeInvisibleDeveloperKeySuccessful())
})
.catch((err) => dispatch(actions.makeInvisibleDeveloperKeyFailed(err)));
};
actions.makeVisibleDeveloperKey = (developerKey) => (dispatch, _getState) => {
dispatch(actions.makeVisibleDeveloperKeyStart());
const url = `/api/v1/developer_keys/${developerKey.id}`
axios.put(url,
{
developer_key: {visible: true}
}
)
.then((response) => {
dispatch(actions.listDeveloperKeysReplace(response.data))
dispatch(actions.makeVisibleDeveloperKeySuccessful())
})
.catch((err) => dispatch(actions.makeVisibleDeveloperKeyFailed(err)));
};
actions.deleteDeveloperKey = (developerKey) => (dispatch, _getState) => {
dispatch(actions.deleteDeveloperKeyStart());

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2018 - 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 ACTION_NAMES from '../actions/developerKeysActions'
const initialState = {
makeInvisibleDeveloperKeyPending: false,
makeInvisibleDeveloperKeySuccessful: false,
makeInvisibleDeveloperKeyError: null,
}
const developerKeysHandlers = {
[ACTION_NAMES.MAKE_INVISIBLE_DEVELOPER_KEY_START]: (state, _action) => ({
...state,
makeInvisibleDeveloperKeyPending: true,
makeInvisibleDeveloperKeySuccessful: false,
makeInvisibleDeveloperKeyError: null
}),
[ACTION_NAMES.MAKE_INVISIBLE_DEVELOPER_KEY_SUCCESSFUL]: (state, _action) => ({
...state,
makeInvisibleDeveloperKeyPending: false,
makeInvisibleDeveloperKeySuccessful: true,
}),
[ACTION_NAMES.MAKE_INVISIBLE_DEVELOPER_KEY_FAILED]: (state, action) => ({
...state,
makeInvisibleDeveloperKeyPending: false,
makeInvisibleDeveloperKeyError: action.payload
}),
};
export default (state = initialState, action) => {
if (developerKeysHandlers[action.type]) {
return developerKeysHandlers[action.type](state, action)
} else {
return state
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2018 - 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 ACTION_NAMES from '../actions/developerKeysActions'
const initialState = {
makeVisibleDeveloperKeyPending: false,
makeVisibleDeveloperKeySuccessful: false,
makeVisibleDeveloperKeyError: null,
}
const developerKeysHandlers = {
[ACTION_NAMES.MAKE_VISIBLE_DEVELOPER_KEY_START]: (state, _action) => ({
...state,
makeVisibleDeveloperKeyPending: true,
makeVisibleDeveloperKeySuccessful: false,
makeVisibleDeveloperKeyError: null
}),
[ACTION_NAMES.MAKE_VISIBLE_DEVELOPER_KEY_SUCCESSFUL]: (state, _action) => ({
...state,
makeVisibleDeveloperKeyPending: false,
makeVisibleDeveloperKeySuccessful: true,
}),
[ACTION_NAMES.MAKE_VISIBLE_DEVELOPER_KEY_FAILED]: (state, action) => ({
...state,
makeVisibleDeveloperKeyPending: false,
makeVisibleDeveloperKeyError: action.payload
}),
};
export default (state = initialState, action) => {
if (developerKeysHandlers[action.type]) {
return developerKeysHandlers[action.type](state, action)
} else {
return state
}
}

View File

@ -23,6 +23,8 @@ import deactivateDeveloperKeyReducer from '../reducers/deactivateDeveloperKeyRed
import activateDeveloperKeyReducer from '../reducers/activateDeveloperKeyReducer'
import deleteDeveloperKeyReducer from '../reducers/deleteDeveloperKeyReducer'
import createOrEditDeveloperKeyReducer from '../reducers/createOrEditDeveloperKeyReducer'
import makeVisibleDeveloperKeyReducer from '../reducers/makeVisibleDeveloperKeyReducer'
import makeInvisibleDeveloperKeyReducer from '../reducers/makeInvisibleDeveloperKeyReducer'
const createStoreWithMiddleware = applyMiddleware(
ReduxThunk
@ -33,7 +35,9 @@ const developerKeysReducer = combineReducers({
deactivateDeveloperKey: deactivateDeveloperKeyReducer,
activateDeveloperKey: activateDeveloperKeyReducer,
deleteDeveloperKey: deleteDeveloperKeyReducer,
createOrEditDeveloperKey: createOrEditDeveloperKeyReducer
createOrEditDeveloperKey: createOrEditDeveloperKeyReducer,
makeVisibleDeveloperKey: makeVisibleDeveloperKeyReducer,
makeInvisibleDeveloperKey: makeInvisibleDeveloperKeyReducer
});
export default createStoreWithMiddleware(developerKeysReducer)

View File

@ -62,6 +62,7 @@ function props() {
user_name: "billy bob",
vendor_code: "b3w9w9bf",
workflow_state: "active",
visible: false,
}
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2018 - 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 actions from 'jsx/developer_keys/actions/developerKeysActions'
import reducer from 'jsx/developer_keys/reducers/makeInvisibleDeveloperKeyReducer'
QUnit.module('makeInvisibleDeveloperKeyReducer');
const defaults = reducer(undefined, {})
test('there are defaults', () => {
equal(defaults.makeInvisibleDeveloperKeyPending, false);
equal(defaults.makeInvisibleDeveloperKeySuccessful, false);
equal(defaults.makeInvisibleDeveloperKeyError, null);
});
test('responds to makeInvisibleDeveloperKeyStart', () => {
const state = {
makeInvisibleDeveloperKeyPending: false,
makeInvisibleDeveloperKeySuccessful: true,
makeInvisibleDeveloperKeyError: {}
};
const action = actions.makeInvisibleDeveloperKeyStart();
const newState = reducer(state, action);
equal(newState.makeInvisibleDeveloperKeyPending, true);
equal(newState.makeInvisibleDeveloperKeySuccessful, false);
equal(newState.makeInvisibleDeveloperKeyError, null);
});
test('responds to makeInvisibleDeveloperKeySuccessful', () => {
const state = {
makeInvisibleDeveloperKeyPending: true,
makeInvisibleDeveloperKeySuccessful: false,
};
const payload = {};
const action = actions.makeInvisibleDeveloperKeySuccessful(payload);
const newState = reducer(state, action);
equal(newState.makeInvisibleDeveloperKeyPending, false);
equal(newState.makeInvisibleDeveloperKeySuccessful, true);
});
test('responds to makeInvisibleDeveloperKeyFailed', () => {
const state = {
makeInvisibleDeveloperKeyPending: true,
makeInvisibleDeveloperKeyError: null
};
const error = {};
const action = actions.makeInvisibleDeveloperKeyFailed(error);
const newState = reducer(state, action);
equal(newState.makeInvisibleDeveloperKeyPending, false);
equal(newState.makeInvisibleDeveloperKeyError, error);
});

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2018 - 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 actions from 'jsx/developer_keys/actions/developerKeysActions'
import reducer from 'jsx/developer_keys/reducers/makeVisibleDeveloperKeyReducer'
QUnit.module('makeVisibleDeveloperKeyReducer');
const defaults = reducer(undefined, {})
test('there are defaults', () => {
equal(defaults.makeVisibleDeveloperKeyPending, false);
equal(defaults.makeVisibleDeveloperKeySuccessful, false);
equal(defaults.makeVisibleDeveloperKeyError, null);
});
test('responds to makeVisibleDeveloperKeyStart', () => {
const state = {
makeVisibleDeveloperKeyPending: false,
makeVisibleDeveloperKeySuccessful: true,
makeVisibleDeveloperKeyError: {}
};
const action = actions.makeVisibleDeveloperKeyStart();
const newState = reducer(state, action);
equal(newState.makeVisibleDeveloperKeyPending, true);
equal(newState.makeVisibleDeveloperKeySuccessful, false);
equal(newState.makeVisibleDeveloperKeyError, null);
});
test('responds to makeVisibleDeveloperKeySuccessful', () => {
const state = {
makeVisibleDeveloperKeyPending: true,
makeVisibleDeveloperKeySuccessful: false,
};
const payload = {};
const action = actions.makeVisibleDeveloperKeySuccessful(payload);
const newState = reducer(state, action);
equal(newState.makeVisibleDeveloperKeyPending, false);
equal(newState.makeVisibleDeveloperKeySuccessful, true);
});
test('responds to makeVisibleDeveloperKeyFailed', () => {
const state = {
makeVisibleDeveloperKeyPending: true,
makeVisibleDeveloperKeyError: null
};
const error = {};
const action = actions.makeVisibleDeveloperKeyFailed(error);
const newState = reducer(state, action);
equal(newState.makeVisibleDeveloperKeyPending, false);
equal(newState.makeVisibleDeveloperKeyError, error);
});