Skip to content

Commit 27e596f

Browse files
committed
fix: validate duplicates on page load and add empty role option to mapping selects
Run duplicate detection on initial page load so pre-existing conflicts are flagged immediately. Add a "No Role" empty option to both WorkOS and WordPress role dropdowns in saved and new rows.
1 parent dd431ba commit 27e596f

2 files changed

Lines changed: 126 additions & 19 deletions

File tree

‎src/WorkOS/Admin/Settings.php‎

Lines changed: 122 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,17 @@ private function enqueue_role_mapping_assets(): void {
226226
$asset['version']
227227
);
228228

229-
$wp_roles = \WorkOS\Sync\RoleMapper::get_wp_roles();
229+
$wp_roles = \WorkOS\Sync\RoleMapper::get_wp_roles();
230+
$workos_roles = $this->get_workos_org_roles();
230231
wp_add_inline_script(
231232
'workos-role-mapping',
232-
'window.workosRoleMapping = ' . wp_json_encode( [ 'wpRoles' => $wp_roles ] ) . ';',
233+
'window.workosRoleMapping = ' . wp_json_encode(
234+
[
235+
'wpRoles' => $wp_roles,
236+
'workosRoles' => $workos_roles,
237+
'env' => $this->get_editing_environment(),
238+
]
239+
) . ';',
233240
'before'
234241
);
235242
}
@@ -1258,13 +1265,88 @@ private function render_webhook_events_modal(): void {
12581265
echo '</div>';
12591266
}
12601267

1268+
/**
1269+
* Fetch WorkOS organization roles for the editing environment.
1270+
*
1271+
* Calls the WorkOS API and caches the result in a 5-minute transient.
1272+
* Returns a slug => name map, or empty array on error.
1273+
*
1274+
* @return array<string, string> WorkOS role slug => role name.
1275+
*/
1276+
private function get_workos_org_roles(): array {
1277+
$env = $this->get_editing_environment();
1278+
$options = get_option( "workos_{$env}", [] );
1279+
$org_id = $options['organization_id'] ?? '';
1280+
1281+
if ( empty( $org_id ) ) {
1282+
return [];
1283+
}
1284+
1285+
$cache_key = 'workos_org_roles_' . $org_id;
1286+
$cached = get_transient( $cache_key );
1287+
1288+
if ( false !== $cached ) {
1289+
return is_array( $cached ) ? $cached : [];
1290+
}
1291+
1292+
if ( ! workos()->is_enabled() ) {
1293+
return [];
1294+
}
1295+
1296+
$result = workos()->api()->list_organization_roles( $org_id );
1297+
1298+
if ( is_wp_error( $result ) || empty( $result['data'] ) ) {
1299+
// Cache empty result briefly to avoid repeated failing calls.
1300+
set_transient( $cache_key, [], MINUTE_IN_SECONDS );
1301+
return [];
1302+
}
1303+
1304+
$roles = [];
1305+
foreach ( $result['data'] as $role ) {
1306+
$slug = $role['slug'] ?? '';
1307+
$name = $role['name'] ?? $slug;
1308+
if ( $slug ) {
1309+
$roles[ $slug ] = $name;
1310+
}
1311+
}
1312+
1313+
set_transient( $cache_key, $roles, 5 * MINUTE_IN_SECONDS );
1314+
1315+
return $roles;
1316+
}
1317+
12611318
/**
12621319
* Render the role mapping table.
12631320
*/
12641321
public function render_role_map(): void {
1265-
$map = \WorkOS\Sync\RoleMapper::get_role_map();
1266-
$wp_roles = \WorkOS\Sync\RoleMapper::get_wp_roles();
1267-
$env = $this->get_editing_environment();
1322+
$map = \WorkOS\Sync\RoleMapper::get_role_map();
1323+
$wp_roles = \WorkOS\Sync\RoleMapper::get_wp_roles();
1324+
$workos_roles = $this->get_workos_org_roles();
1325+
$env = $this->get_editing_environment();
1326+
$has_dropdown = ! empty( $workos_roles );
1327+
1328+
// Out-of-sync warning banner.
1329+
$out_of_sync = \WorkOS\Sync\RoleMapper::get_out_of_sync_users();
1330+
if ( ! empty( $out_of_sync ) ) {
1331+
$count = count( $out_of_sync );
1332+
printf(
1333+
'<div class="notice notice-warning inline" id="workos-role-sync-warning"><p>%s <a href="%s">%s</a></p></div>',
1334+
esc_html(
1335+
sprintf(
1336+
/* translators: %d: number of out-of-sync users */
1337+
_n(
1338+
'%d user has a WordPress role that doesn\'t match their WorkOS role mapping.',
1339+
'%d users have a WordPress role that doesn\'t match their WorkOS role mapping.',
1340+
$count,
1341+
'workos'
1342+
),
1343+
$count
1344+
)
1345+
),
1346+
esc_url( admin_url( 'users.php?workos_sync_status=out_of_sync' ) ),
1347+
esc_html__( 'View affected users', 'workos' )
1348+
);
1349+
}
12681350

12691351
echo '<table id="workos-role-map-table" class="widefat"><thead><tr>';
12701352
echo '<th>' . esc_html__( 'WorkOS Role', 'workos' ) . '</th>';
@@ -1274,13 +1356,28 @@ public function render_role_map(): void {
12741356

12751357
foreach ( $map as $workos_role => $wp_role ) {
12761358
echo '<tr class="workos-role-map-row"><td>';
1277-
printf(
1278-
'<input type="text" name="workos_%s[role_map][keys][]" value="%s" class="regular-text" />',
1279-
esc_attr( $env ),
1280-
esc_attr( $workos_role )
1281-
);
1359+
if ( $has_dropdown ) {
1360+
printf( '<select name="workos_%s[role_map][keys][]">', esc_attr( $env ) );
1361+
echo '<option value="">' . esc_html__( '— No Role —', 'workos' ) . '</option>';
1362+
foreach ( $workos_roles as $slug => $name ) {
1363+
printf(
1364+
'<option value="%s" %s>%s</option>',
1365+
esc_attr( $slug ),
1366+
selected( $workos_role, $slug, false ),
1367+
esc_html( $name )
1368+
);
1369+
}
1370+
echo '</select>';
1371+
} else {
1372+
printf(
1373+
'<input type="text" name="workos_%s[role_map][keys][]" value="%s" class="regular-text" />',
1374+
esc_attr( $env ),
1375+
esc_attr( $workos_role )
1376+
);
1377+
}
12821378
echo '</td><td>';
12831379
printf( '<select name="workos_%s[role_map][values][]">', esc_attr( $env ) );
1380+
echo '<option value="">' . esc_html__( '— No Role —', 'workos' ) . '</option>';
12841381
foreach ( $wp_roles as $slug => $name ) {
12851382
printf(
12861383
'<option value="%s" %s>%s</option>',
@@ -1295,14 +1392,23 @@ public function render_role_map(): void {
12951392

12961393
// Empty row for no-JS fallback.
12971394
echo '<tr class="workos-role-map-row"><td>';
1298-
printf(
1299-
'<input type="text" name="workos_%s[role_map][keys][]" value="" class="regular-text" placeholder="%s" />',
1300-
esc_attr( $env ),
1301-
esc_attr__( 'New WorkOS role...', 'workos' )
1302-
);
1395+
if ( $has_dropdown ) {
1396+
printf( '<select name="workos_%s[role_map][keys][]">', esc_attr( $env ) );
1397+
echo '<option value="">' . esc_html__( '— No Role —', 'workos' ) . '</option>';
1398+
foreach ( $workos_roles as $slug => $name ) {
1399+
printf( '<option value="%s">%s</option>', esc_attr( $slug ), esc_html( $name ) );
1400+
}
1401+
echo '</select>';
1402+
} else {
1403+
printf(
1404+
'<input type="text" name="workos_%s[role_map][keys][]" value="" class="regular-text" placeholder="%s" />',
1405+
esc_attr( $env ),
1406+
esc_attr__( 'New WorkOS role...', 'workos' )
1407+
);
1408+
}
13031409
echo '</td><td>';
13041410
printf( '<select name="workos_%s[role_map][values][]">', esc_attr( $env ) );
1305-
echo '<option value="">' . esc_html__( '— Select —', 'workos' ) . '</option>';
1411+
echo '<option value="">' . esc_html__( '— No Role —', 'workos' ) . '</option>';
13061412
foreach ( $wp_roles as $slug => $name ) {
13071413
printf( '<option value="%s">%s</option>', esc_attr( $slug ), esc_html( $name ) );
13081414
}

‎src/js/role-mapping.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ import '../css/role-mapping.css';
105105

106106
const blank = document.createElement( 'option' );
107107
blank.value = '';
108-
blank.textContent = '\u2014 Select \u2014';
108+
blank.textContent = '\u2014 No Role \u2014';
109109
select.appendChild( blank );
110110

111111
Object.keys( wpRoles ).forEach(
@@ -132,7 +132,7 @@ import '../css/role-mapping.css';
132132

133133
const blank = document.createElement( 'option' );
134134
blank.value = '';
135-
blank.textContent = '\u2014 Select \u2014';
135+
blank.textContent = '\u2014 No Role \u2014';
136136
select.appendChild( blank );
137137

138138
Object.keys( workosRoles ).forEach(
@@ -297,6 +297,7 @@ import '../css/role-mapping.css';
297297
} );
298298
}
299299

300-
// Initial visibility check.
300+
// Initial visibility and duplicate checks.
301301
updateRemoveButtons();
302+
validateDuplicates();
302303
} )();

0 commit comments

Comments
 (0)