Skip to content

Commit 340b18c

Browse files
authored
Merge pull request #292 from ubenzer/ids-4707
IDS-4707 - Switch from InputCombo to InputText if there are too many connections to list
2 parents 855bfbc + de76206 commit 340b18c

File tree

8 files changed

+80
-23
lines changed

8 files changed

+80
-23
lines changed

client/actions/user.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import * as constants from '../constants';
55
import { fetchUserLogs } from './userLog';
66
import { fetchUserDevices } from './userDevice';
77
import { getAccessLevel } from './auth';
8-
import { removeBlockedIPs } from "../reducers/removeBlockedIPs";
98

109
const addRequiredTextParam = (url, languageDictionary) => {
1110
languageDictionary = languageDictionary || {};
@@ -77,10 +76,15 @@ export function createUser(user, languageDictionary) {
7776
export function requestCreateUser(memberships) {
7877
return (dispatch, getState) => {
7978
const connections = getState().connections.get('records').toJS();
79+
80+
const connection = connections.length === 0
81+
? null
82+
: connections && connections.length && connections[0].name
83+
8084
dispatch({
8185
type: constants.REQUEST_CREATE_USER,
8286
payload: {
83-
connection: connections && connections.length && connections[0].name,
87+
connection,
8488
memberships: memberships && memberships.length === 1 ? [ memberships[0] ] : [ ]
8589
}
8690
});

client/containers/Users/Users.jsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import './Users.styles.css';
1515
class Users extends Component {
1616
static propTypes = {
1717
loading: PropTypes.bool.isRequired,
18+
connectionsLoading: PropTypes.bool.isRequired,
1819
error: PropTypes.string,
1920
users: PropTypes.array,
2021
connections: PropTypes.array,
@@ -44,7 +45,9 @@ class Users extends Component {
4445

4546
componentWillMount = () => {
4647
this.props.fetchUsers();
47-
this.props.fetchConnections();
48+
if (!this.props.connectionsLoading) {
49+
this.props.fetchConnections();
50+
}
4851
};
4952

5053
onPageChange = (page) => {
@@ -77,7 +80,7 @@ class Users extends Component {
7780
error,
7881
users,
7982
total,
80-
connections,
83+
connectionsLoading,
8184
accessLevel,
8285
nextPage,
8386
pages,
@@ -102,7 +105,7 @@ class Users extends Component {
102105
<div className="row content-header">
103106
<div className="col-xs-12 user-table-content">
104107
<h1>{languageDictionary.usersTitle || 'Users'}</h1>
105-
{(connections.length && role > 0 && showCreateUser) ?
108+
{( !connectionsLoading && role > 0 && showCreateUser) ?
106109
<button id="create-user-button" className="btn btn-success pull-right new" onClick={this.createUser}>
107110
<i className="icon-budicon-473"></i>
108111
{languageDictionary.createUserButtonText || 'Create User'}
@@ -158,6 +161,7 @@ function mapStateToProps(state) {
158161
loading: state.users.get('loading'),
159162
users: state.users.get('records').toJS(),
160163
connections: state.connections.get('records').toJS(),
164+
connectionsLoading: state.connections.get('loading'),
161165
total: state.users.get('total'),
162166
nextPage: state.users.get('nextPage'),
163167
pages: state.users.get('pages'),

client/utils/useDefaultFields.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@ export const useUsernameField = (isEditField, fields, connections, hasSelectedCo
1515
const type = isEditField ? 'edit' : 'create';
1616
const selectedConnection = _.find(connections, (conn) => conn.name === hasSelectedConnection);
1717
const requireUsername = selectedConnection && selectedConnection.options ? selectedConnection.options.requires_username : false;
18-
const noUsername = !requireUsername && (!initialValues || !initialValues.username);
18+
const noUsername = connections.length > 0
19+
? !requireUsername && (!initialValues || !initialValues.username)
20+
// if we have no connections, we *might* need a username field, we don't know -
21+
// because we don't have the connections to check
22+
: false;
1923

2024
const defaults = {
2125
property: 'username',
2226
label: 'Username',
2327
disable: noUsername,
2428
[type]: {
2529
type: 'text',
26-
required: true
30+
// if we have no connections we should show the field but not require it
31+
required: connections.length > 0
2732
}
2833
};
2934

@@ -57,18 +62,22 @@ export const useMembershipsField = (isEditField, fields, hasMembership, membersh
5762

5863
export const useConnectionsField = (isEditField, fields, connections, onConnectionChange) => {
5964
const type = isEditField ? 'edit' : 'create';
60-
if (!connections || connections.length <= 1) {
65+
// if we have exactly one connection then don't show this field and use that connection
66+
// however if we have zero connections, we should show the free text connections field
67+
if (!connections || connections.length === 1) {
6168
return _.remove(fields, { property: 'connection' });
6269
}
6370

71+
const isConnectionLimitExceeded = connections.length === 0;
72+
6473
const defaults = {
6574
property: 'connection',
66-
label: 'Connection',
75+
label: isConnectionLimitExceeded ? 'Connection Name' : 'Connection',
6776
[type]: {
6877
required: true,
69-
type: 'select',
70-
component: 'InputCombo',
71-
options: connections.map(conn => ({ value: conn.name, label: conn.name })),
78+
type: isConnectionLimitExceeded ? 'text' : 'select',
79+
component: isConnectionLimitExceeded ? 'InputText' : 'InputCombo',
80+
options: isConnectionLimitExceeded ? undefined : connections.map(conn => ({ value: conn.name, label: conn.name })),
7281
onChange: onConnectionChange
7382
}
7483
};

package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,13 @@
1414
"deploy": "a0-ext deploy --package ./dist/package.zip --url http://0.0.0.0:3000/api/extensions",
1515
"client:build": "a0-ext build:client ./client/app.jsx ./dist/client",
1616
"extension:build": "a0-ext build:server ./webtask.js ./dist && cp ./dist/auth0-delegated-admin.extension.$npm_package_version.js ./build/bundle.js && cp ./webtask.json ./dist/webtask.json",
17-
"serve:dev": "cross-env NODE_ENV=development nodemon -e js --ignore assets/app/ --ignore build/webpack/ --ignore client/ --ignore server/data.json --ignore node_modules/ ./build/webpack/server.js",
17+
"serve:dev": "cross-env NODE_ENV=development NODE_OPTIONS=--openssl-legacy-provider nodemon -e js --ignore assets/app/ --ignore build/webpack/ --ignore client/ --ignore server/data.json --ignore node_modules/ ./build/webpack/server.js",
1818
"serve:prod": "cross-env NODE_ENV=production node index.js",
1919
"test": "cross-env NODE_ENV=test nyc --reporter=lcov mocha --require ignore-styles tests/mocha.js './tests/**/*.tests.js'",
2020
"test:watch": "cross-env NODE_ENV=test mocha --require ignore-styles tests/mocha.js './tests/**/*.tests.js' --watch",
2121
"test:pre": "npm run test:clean && npm run lint:js",
2222
"test:clean": "rimraf ./coverage && rimraf ./.nyc_output",
23-
"extension:size": "cross-env NODE_ENV=production webpack -p --config ./build/extension/webpack.config.js --json > ./build/extension/bundle-size.json && node ./build/extension/bundle-size.js",
24-
"snyk-protect": "snyk protect",
25-
"prepare": "npm run snyk-protect"
23+
"extension:size": "cross-env NODE_ENV=production webpack -p --config ./build/extension/webpack.config.js --json > ./build/extension/bundle-size.json && node ./build/extension/bundle-size.js"
2624
},
2725
"keywords": [
2826
"auth0",

server/lib/multipartRequest.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import Promise from 'bluebird';
22
import { ArgumentError } from 'auth0-extension-tools';
33

4-
export default function(client, entity, opts = {}, perPage = 50, concurrency = 5) {
4+
export default function(client, entity, opts = {}, fetchOptions = {} ) {
5+
const perPage = fetchOptions.perPage || 50;
6+
const concurrency = fetchOptions.concurrency || 5;
7+
const limit = fetchOptions.limit || null;
8+
59
if (client === null || client === undefined) {
610
throw new ArgumentError('Must provide a auth0 client object.');
711
}
@@ -13,6 +17,7 @@ export default function(client, entity, opts = {}, perPage = 50, concurrency = 5
1317
const getter = client[entity].getAll;
1418
const options = { ...opts, per_page: perPage };
1519
const result = [];
20+
1621
let total = 0;
1722
let pageCount = 0;
1823

@@ -21,6 +26,14 @@ export default function(client, entity, opts = {}, perPage = 50, concurrency = 5
2126
.then((response) => {
2227
total = response.total || 0;
2328
pageCount = Math.ceil(total / perPage);
29+
30+
// if the total exceeds the limit, don't fetch any more connections from api2
31+
// we get some from the initial request to get totals, but we'll ignore them
32+
if (limit && (total > limit)) {
33+
pageCount = 1;
34+
return null;
35+
}
36+
2437
const data = response[entity] || response || [];
2538
data.forEach(item => result.push(item));
2639
return null;
@@ -36,6 +49,13 @@ export default function(client, entity, opts = {}, perPage = 50, concurrency = 5
3649
const getAll = () =>
3750
getTotals()
3851
.then(() => {
52+
// the number of connections exceeds the limit we can handle:
53+
// - don't return any to the frontend
54+
// - will use a free text box in the user creation dialogue
55+
if (limit && (total > limit)) {
56+
return result;
57+
}
58+
3959
if (total === 0 || result.length >= total) {
4060
return result;
4161
}

server/routes/connections.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@ import { Router } from 'express';
33

44
import multipartRequest from '../lib/multipartRequest';
55

6+
// This is the number of connections in a tenant which the DAE can reasonably handle. More than this and it fails to
7+
// finish loading connections and the "create user" button is never shown. If there are more connections than this
8+
// in the tenant, we will return zero connections to the front end, and it will use a free text box for connection name
9+
// in the create user dialogue.
10+
const CONNECTIONS_FETCH_LIMIT = 20000;
11+
612
export default (scriptManager) => {
713
const api = Router();
814
api.get('/', (req, res, next) => {
9-
multipartRequest(req.auth0, 'connections', { strategy: 'auth0', fields: 'id,name,strategy,options' })
15+
multipartRequest(
16+
req.auth0,
17+
'connections',
18+
{ strategy: 'auth0', fields: 'id,name,strategy,options' },
19+
{ limit: CONNECTIONS_FETCH_LIMIT, perPage: 100 }
20+
)
1021
.then((connections) => {
1122
global.connections = connections.map(conn => ({ name: conn.name, id: conn.id }));
1223
const settingsContext = {

tests/client/components/Users/UserForm.tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ describe('#Client-Components-UserForm', () => {
249249
someOtherKey: 'Some other value'
250250
};
251251

252-
const Component = renderComponent(languageDictionary);
252+
const Component = renderComponent(everythingOptions, languageDictionary);
253253

254254
expect(Component.length).to.be.greaterThan(0);
255255
expect(Component.find(Button).filterWhere(element => element.text() === 'Cancel').length).to.equal(1);

tests/client/utils/useDefaultFields.tests.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,24 @@ describe('Client-Utils-useDefaultFields', () => {
247247

248248
it('empty array population skip', () => {
249249
const fields = [];
250-
const target = [];
250+
const target1 = [{
251+
property: 'connection',
252+
label: 'Connection Name',
253+
edit: {
254+
required: true,
255+
type: 'text',
256+
component: 'InputText',
257+
options: undefined,
258+
onChange: undefined
259+
}
260+
}];
261+
const target2 = [];
251262

252263
useDefaultFields.useConnectionsField(true, fields, []);
253-
expect(fields).to.deep.equal(target);
264+
expect(fields).to.deep.equal(target1);
254265

255266
useDefaultFields.useConnectionsField(true, fields, connection);
256-
expect(fields).to.deep.equal(target);
267+
expect(fields).to.deep.equal(target2);
257268
});
258269

259270
it('pre populated array', () => {
@@ -321,7 +332,7 @@ describe('Client-Utils-useDefaultFields', () => {
321332
}];
322333
const target = [];
323334

324-
useDefaultFields.useConnectionsField(true, fields, []);
335+
useDefaultFields.useConnectionsField(true, fields, connection);
325336
expect(fields).to.deep.equal(target);
326337
});
327338

0 commit comments

Comments
 (0)