Add APPID + APPREFID searchable picker to Granite Loader

Load CMDB app instance data (4797 entries) from static JSON and present
a searchable dropdown in the APP_ID_ASSET_TAG column. Users search by
app name or APP ID, select an entry, and the field populates with the
'APP_ID - APPREFID' format required by Granite.

- Convert Cherwell CMDB CSV export to frontend/public/cmdb-appids.json
- LoaderModal: load APPID options on open, provide via getPicklist()
  helper that overrides COLUMN_PICKLISTS for the APP_ID_ASSET_TAG field
- Options display as 'APP_NAME (APP_ID) [ENV]' for easy identification
- Works in both bulk defaults and per-cell inline editing
This commit is contained in:
Jordan Ramos
2026-06-30 14:17:21 -06:00
parent 93d68bccfe
commit c338e6fa3b
2 changed files with 32 additions and 5 deletions

File diff suppressed because one or more lines are too long

View File

@@ -18,6 +18,7 @@ import { generateLoaderXlsx, generateFilename } from '../utils/graniteLoaderExpo
import { COLUMN_PICKLISTS } from '../utils/graniteLoaderPicklists';
import SearchableSelect from './SearchableSelect';
// ⚠️ CONVENTION: Use relative API paths (no absolute URLs). The fallback 'http://localhost:3001/api' should be '/api' instead.
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
// ---------------------------------------------------------------------------
@@ -68,6 +69,7 @@ export default function LoaderModal({ isOpen, onClose, initialDevices }) {
const [pasteInput, setPasteInput] = useState('');
const [expandedGroups, setExpandedGroups] = useState(new Set(['Identification', 'Responsible Org']));
const [validationWarnings, setValidationWarnings] = useState([]);
const [appIdOptions, setAppIdOptions] = useState(null);
// --- Initialize ---
useEffect(() => {
@@ -77,7 +79,22 @@ export default function LoaderModal({ isOpen, onClose, initialDevices }) {
.then(r => r.json())
.then(d => setCardConfigured(d.configured === true))
.catch(() => setCardConfigured(false));
}, [isOpen]);
// Load CMDB APPID data for Granite Loader APPID picker
if (!appIdOptions) {
// ⚠️ CONVENTION: Use credentials: 'include' on all fetch calls (missing here)
fetch('/cmdb-appids.json', { credentials: 'include' })
.then(r => r.ok ? r.json() : [])
.then(data => {
// Build searchable options: label shows "APP_NAME (APP_ID) [ENV]", value is "APP_ID - APPREFID"
const opts = data.map(d => ({
label: `${d.app_name} (${d.app_id}) [${d.env}]`,
value: `${d.app_id} - ${d.apprefid}`,
}));
setAppIdOptions(opts);
})
.catch(() => setAppIdOptions([]));
}
}, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
// Populate devices from initialDevices or reset
useEffect(() => {
@@ -133,6 +150,15 @@ export default function LoaderModal({ isOpen, onClose, initialDevices }) {
});
}, []);
// --- Picklist helper: returns options array for a column, or null if no picklist ---
// Uses dynamic CMDB data for APP_ID_ASSET_TAG, static picklists for others
const getPicklist = useCallback((colId) => {
if (colId === 'APP_ID_ASSET_TAG' && appIdOptions && appIdOptions.length > 0) {
return appIdOptions;
}
return COLUMN_PICKLISTS[colId] || null;
}, [appIdOptions]);
// --- Ordered selected columns (canonical order) ---
const orderedColumns = useMemo(() => {
return LOADER_COLUMNS.filter(col => selectedColumns.has(col.id));
@@ -496,10 +522,10 @@ export default function LoaderModal({ isOpen, onClose, initialDevices }) {
<label style={{ fontSize: '0.6rem', color: '#64748B', display: 'block', marginBottom: '0.15rem' }}>
{col.id}
</label>
{COLUMN_PICKLISTS[col.id] ? (
{getPicklist(col.id) ? (
<SearchableSelect
value={bulkDefaults[col.id] || ''}
options={COLUMN_PICKLISTS[col.id]}
options={getPicklist(col.id)}
onChange={(val) => setBulkDefault(col.id, val)}
placeholder={`Default for all rows`}
autoFocus={false}
@@ -556,10 +582,10 @@ export default function LoaderModal({ isOpen, onClose, initialDevices }) {
onClick={() => !isEditing && startEdit(rowIdx, col.id)}
>
{isEditing ? (
COLUMN_PICKLISTS[col.id] ? (
getPicklist(col.id) ? (
<SearchableSelect
value={editValue}
options={COLUMN_PICKLISTS[col.id]}
options={getPicklist(col.id)}
onChange={(val) => {
setOverrides(prev => ({
...prev,