Skip to content

Commit

Permalink
Merge pull request #292 from ubenzer/ids-4707
Browse files Browse the repository at this point in the history
IDS-4707 - Switch from InputCombo to InputText if there are too many connections to list
  • Loading branch information
sauntimo authored Jun 5, 2024
2 parents 855bfbc + de76206 commit 340b18c
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 23 deletions.
8 changes: 6 additions & 2 deletions client/actions/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import * as constants from '../constants';
import { fetchUserLogs } from './userLog';
import { fetchUserDevices } from './userDevice';
import { getAccessLevel } from './auth';
import { removeBlockedIPs } from "../reducers/removeBlockedIPs";

const addRequiredTextParam = (url, languageDictionary) => {
languageDictionary = languageDictionary || {};
Expand Down Expand Up @@ -77,10 +76,15 @@ export function createUser(user, languageDictionary) {
export function requestCreateUser(memberships) {
return (dispatch, getState) => {
const connections = getState().connections.get('records').toJS();

const connection = connections.length === 0
? null
: connections && connections.length && connections[0].name

dispatch({
type: constants.REQUEST_CREATE_USER,
payload: {
connection: connections && connections.length && connections[0].name,
connection,
memberships: memberships && memberships.length === 1 ? [ memberships[0] ] : [ ]
}
});
Expand Down
10 changes: 7 additions & 3 deletions client/containers/Users/Users.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import './Users.styles.css';
class Users extends Component {
static propTypes = {
loading: PropTypes.bool.isRequired,
connectionsLoading: PropTypes.bool.isRequired,
error: PropTypes.string,
users: PropTypes.array,
connections: PropTypes.array,
Expand Down Expand Up @@ -44,7 +45,9 @@ class Users extends Component {

componentWillMount = () => {
this.props.fetchUsers();
this.props.fetchConnections();
if (!this.props.connectionsLoading) {
this.props.fetchConnections();
}
};

onPageChange = (page) => {
Expand Down Expand Up @@ -77,7 +80,7 @@ class Users extends Component {
error,
users,
total,
connections,
connectionsLoading,
accessLevel,
nextPage,
pages,
Expand All @@ -102,7 +105,7 @@ class Users extends Component {
<div className="row content-header">
<div className="col-xs-12 user-table-content">
<h1>{languageDictionary.usersTitle || 'Users'}</h1>
{(connections.length && role > 0 && showCreateUser) ?
{( !connectionsLoading && role > 0 && showCreateUser) ?
<button id="create-user-button" className="btn btn-success pull-right new" onClick={this.createUser}>
<i className="icon-budicon-473"></i>
{languageDictionary.createUserButtonText || 'Create User'}
Expand Down Expand Up @@ -158,6 +161,7 @@ function mapStateToProps(state) {
loading: state.users.get('loading'),
users: state.users.get('records').toJS(),
connections: state.connections.get('records').toJS(),
connectionsLoading: state.connections.get('loading'),
total: state.users.get('total'),
nextPage: state.users.get('nextPage'),
pages: state.users.get('pages'),
Expand Down
23 changes: 16 additions & 7 deletions client/utils/useDefaultFields.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@ export const useUsernameField = (isEditField, fields, connections, hasSelectedCo
const type = isEditField ? 'edit' : 'create';
const selectedConnection = _.find(connections, (conn) => conn.name === hasSelectedConnection);
const requireUsername = selectedConnection && selectedConnection.options ? selectedConnection.options.requires_username : false;
const noUsername = !requireUsername && (!initialValues || !initialValues.username);
const noUsername = connections.length > 0
? !requireUsername && (!initialValues || !initialValues.username)
// if we have no connections, we *might* need a username field, we don't know -
// because we don't have the connections to check
: false;

const defaults = {
property: 'username',
label: 'Username',
disable: noUsername,
[type]: {
type: 'text',
required: true
// if we have no connections we should show the field but not require it
required: connections.length > 0
}
};

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

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

const isConnectionLimitExceeded = connections.length === 0;

const defaults = {
property: 'connection',
label: 'Connection',
label: isConnectionLimitExceeded ? 'Connection Name' : 'Connection',
[type]: {
required: true,
type: 'select',
component: 'InputCombo',
options: connections.map(conn => ({ value: conn.name, label: conn.name })),
type: isConnectionLimitExceeded ? 'text' : 'select',
component: isConnectionLimitExceeded ? 'InputText' : 'InputCombo',
options: isConnectionLimitExceeded ? undefined : connections.map(conn => ({ value: conn.name, label: conn.name })),
onChange: onConnectionChange
}
};
Expand Down
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
"deploy": "a0-ext deploy --package ./dist/package.zip --url http://0.0.0.0:3000/api/extensions",
"client:build": "a0-ext build:client ./client/app.jsx ./dist/client",
"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",
"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",
"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",
"serve:prod": "cross-env NODE_ENV=production node index.js",
"test": "cross-env NODE_ENV=test nyc --reporter=lcov mocha --require ignore-styles tests/mocha.js './tests/**/*.tests.js'",
"test:watch": "cross-env NODE_ENV=test mocha --require ignore-styles tests/mocha.js './tests/**/*.tests.js' --watch",
"test:pre": "npm run test:clean && npm run lint:js",
"test:clean": "rimraf ./coverage && rimraf ./.nyc_output",
"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",
"snyk-protect": "snyk protect",
"prepare": "npm run snyk-protect"
"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"
},
"keywords": [
"auth0",
Expand Down
22 changes: 21 additions & 1 deletion server/lib/multipartRequest.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import Promise from 'bluebird';
import { ArgumentError } from 'auth0-extension-tools';

export default function(client, entity, opts = {}, perPage = 50, concurrency = 5) {
export default function(client, entity, opts = {}, fetchOptions = {} ) {
const perPage = fetchOptions.perPage || 50;
const concurrency = fetchOptions.concurrency || 5;
const limit = fetchOptions.limit || null;

if (client === null || client === undefined) {
throw new ArgumentError('Must provide a auth0 client object.');
}
Expand All @@ -13,6 +17,7 @@ export default function(client, entity, opts = {}, perPage = 50, concurrency = 5
const getter = client[entity].getAll;
const options = { ...opts, per_page: perPage };
const result = [];

let total = 0;
let pageCount = 0;

Expand All @@ -21,6 +26,14 @@ export default function(client, entity, opts = {}, perPage = 50, concurrency = 5
.then((response) => {
total = response.total || 0;
pageCount = Math.ceil(total / perPage);

// if the total exceeds the limit, don't fetch any more connections from api2
// we get some from the initial request to get totals, but we'll ignore them
if (limit && (total > limit)) {
pageCount = 1;
return null;
}

const data = response[entity] || response || [];
data.forEach(item => result.push(item));
return null;
Expand All @@ -36,6 +49,13 @@ export default function(client, entity, opts = {}, perPage = 50, concurrency = 5
const getAll = () =>
getTotals()
.then(() => {
// the number of connections exceeds the limit we can handle:
// - don't return any to the frontend
// - will use a free text box in the user creation dialogue
if (limit && (total > limit)) {
return result;
}

if (total === 0 || result.length >= total) {
return result;
}
Expand Down
13 changes: 12 additions & 1 deletion server/routes/connections.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ import { Router } from 'express';

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

// This is the number of connections in a tenant which the DAE can reasonably handle. More than this and it fails to
// finish loading connections and the "create user" button is never shown. If there are more connections than this
// in the tenant, we will return zero connections to the front end, and it will use a free text box for connection name
// in the create user dialogue.
const CONNECTIONS_FETCH_LIMIT = 20000;

export default (scriptManager) => {
const api = Router();
api.get('/', (req, res, next) => {
multipartRequest(req.auth0, 'connections', { strategy: 'auth0', fields: 'id,name,strategy,options' })
multipartRequest(
req.auth0,
'connections',
{ strategy: 'auth0', fields: 'id,name,strategy,options' },
{ limit: CONNECTIONS_FETCH_LIMIT, perPage: 100 }
)
.then((connections) => {
global.connections = connections.map(conn => ({ name: conn.name, id: conn.id }));
const settingsContext = {
Expand Down
2 changes: 1 addition & 1 deletion tests/client/components/Users/UserForm.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ describe('#Client-Components-UserForm', () => {
someOtherKey: 'Some other value'
};

const Component = renderComponent(languageDictionary);
const Component = renderComponent(everythingOptions, languageDictionary);

expect(Component.length).to.be.greaterThan(0);
expect(Component.find(Button).filterWhere(element => element.text() === 'Cancel').length).to.equal(1);
Expand Down
19 changes: 15 additions & 4 deletions tests/client/utils/useDefaultFields.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,13 +247,24 @@ describe('Client-Utils-useDefaultFields', () => {

it('empty array population skip', () => {
const fields = [];
const target = [];
const target1 = [{
property: 'connection',
label: 'Connection Name',
edit: {
required: true,
type: 'text',
component: 'InputText',
options: undefined,
onChange: undefined
}
}];
const target2 = [];

useDefaultFields.useConnectionsField(true, fields, []);
expect(fields).to.deep.equal(target);
expect(fields).to.deep.equal(target1);

useDefaultFields.useConnectionsField(true, fields, connection);
expect(fields).to.deep.equal(target);
expect(fields).to.deep.equal(target2);
});

it('pre populated array', () => {
Expand Down Expand Up @@ -321,7 +332,7 @@ describe('Client-Utils-useDefaultFields', () => {
}];
const target = [];

useDefaultFields.useConnectionsField(true, fields, []);
useDefaultFields.useConnectionsField(true, fields, connection);
expect(fields).to.deep.equal(target);
});

Expand Down

0 comments on commit 340b18c

Please sign in to comment.