Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { getName } from '../lib/typeName';

interface GrainMethodValue {
grain: string;
Expand Down Expand Up @@ -27,7 +28,7 @@ export default class GrainMethodTable extends React.Component<GrainMethodTablePr
{value.method}
<br />
<small>
<a href={`#/grain/${value.grain}`}>{value.grain}</a>
<a href={`#/grain/${value.grain}`}>{getName(value.grain)}</a>
Comment thread
ReubenBond marked this conversation as resolved.
Outdated
</small>
</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { getName } from '../lib/typeName';

interface GrainStat {
grainType: string;
Expand Down Expand Up @@ -83,8 +84,8 @@ export default class GrainTable extends React.Component<GrainTableProps, GrainTa
}

renderStat = (stat: AggregatedGrainStat) => {
const parts = stat.grainType.split('.');
const grainClassName = parts[parts.length - 1];
// shorten fully-qualified type names, including generics
const grainClassName = getName(stat.grainType);
const systemGrain = stat.grainType.startsWith('Orleans.');
const dashboardGrain = stat.grainType.startsWith('OrleansDashboard.');
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Page from '../components/page';
import http from '../lib/http';
import DisplayGrainState from '../components/display-grain-state';
import Panel from '../components/panel';
import { getName } from '../lib/typeName';

interface GrainDetailsProps {
grainTypes: string[];
Expand Down Expand Up @@ -68,7 +69,7 @@ export default class GrainDetails extends React.Component<GrainDetailsProps, Gra
<select value={this.state.grainType || ''} className="form-control" onChange={this.handleGrainTypeChange}>
<option disabled value=""> -- Select an grain type -- </option>
{
this.props.grainTypes.map((_item) => <option key={_item} value={_item}>{_item}</option>)
this.props.grainTypes.map((_item) => <option key={_item} value={_item}>{getName(_item)}</option>)
Comment thread
ReubenBond marked this conversation as resolved.
Outdated
}
</select>
</div>
Expand Down
30 changes: 24 additions & 6 deletions src/Dashboard/Orleans.Dashboard.App/src/grains/grain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ import CounterWidget from '../components/counter-widget';
import SiloBreakdown from './silo-table';
import Panel from '../components/panel';
import Page from '../components/page';
import { getName } from '../lib/typeName';

// Format a fully-qualified member name so the type is shortened but the
// member/method part is preserved. Examples:
// - "A.B.C<T>.M" -> "C<T>.M"
// - "A.B.C<T>.N.M" -> "C<T>.N.M"
function formatMemberName(value: string): string {
if (!value) return value;
// find last top-level separator (dot, slash, or '#') not inside generics
let depth = 0;
for (let i = value.length - 1; i >= 0; i--) {
const ch = value[i];
if (ch === '>' || ch === ']') depth++;
else if (ch === '<' || ch === '[') depth--;
else if (depth === 0 && (ch === '.' || ch === '/' || ch === '#')) {
const typePart = value.substring(0, i);
const memberPart = value.substring(i + 1);
return `${getName(typePart)}.${memberPart}`;
}
}

return getName(value);
}

interface GrainMethodValue {
count: number;
Expand Down Expand Up @@ -172,7 +195,7 @@ export default class Grain extends React.Component<GrainProps> {
.map(key => (
<GrainGraph key={key}
stats={this.props.grainStats[key]}
grainMethod={getName(key)}
grainMethod={formatMemberName(key)}
/>
))}
</div>
Expand All @@ -195,8 +218,3 @@ export default class Grain extends React.Component<GrainProps> {
return this.renderGraphs();
}
}

function getName(value: string): string {
const parts = value.split('.');
return parts[parts.length - 1];
}
102 changes: 102 additions & 0 deletions src/Dashboard/Orleans.Dashboard.App/src/lib/typeName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
export function getName(value: string): string {
// Parse a type name and shorten fully-qualified names to their last segment.
// Handles generic type arguments (angle brackets or square brackets) and
// shortens their content recursively. Examples:
// "A.B.C<T.U.V>" -> "C<V>"
// "G<X.Y<Z.W>>" -> "G<Y<W>>"
function trimIdentifier(id: string): string {
if (!id) return id;
id = id.trim();
// Remove assembly details if present: "Type, Assembly"
const comma = id.indexOf(',');
if (comma !== -1) id = id.substring(0, comma).trim();
const parts = id.split('.');
return parts[parts.length - 1];
Comment thread
hendrikdevloed marked this conversation as resolved.
Outdated
}

Comment thread
ReubenBond marked this conversation as resolved.
Outdated
function parseType(str: string): string {
if (!str) return str;
let s = str.trim();

// Find first generic opener: '<' or '['
const lt = s.indexOf('<');
const lb = s.indexOf('[');
let opener = '';
let openerPos = -1;
let closer = '';
if (lt !== -1 && (lb === -1 || lt < lb)) {
opener = '<'; closer = '>'; openerPos = lt;
} else if (lb !== -1) {
opener = '['; closer = ']'; openerPos = lb;
}

if (openerPos === -1) {
return trimIdentifier(s);
}

const main = s.substring(0, openerPos);

// Find matching closer for the opener, honoring nested pairs
let depth = 0;
let end = -1;
for (let i = openerPos; i < s.length; i++) {
const ch = s[i];
if (ch === opener) depth++; else if (ch === closer) {
depth--; if (depth === 0) { end = i; break; }
}
}

if (end === -1) {
return trimIdentifier(s);
}

const inner = s.substring(openerPos + 1, end);
const remainder = s.substring(end + 1);

// Split inner by top-level commas only
const args: string[] = [];
let argStart = 0;
depth = 0;
for (let i = 0; i < inner.length; i++) {
const ch = inner[i];
if (ch === '<' || ch === '[') depth++; else if (ch === '>' || ch === ']') depth--;
else if (ch === ',' && depth === 0) {
args.push(inner.substring(argStart, i));
argStart = i + 1;
Comment thread
ReubenBond marked this conversation as resolved.
Outdated
}
}
args.push(inner.substring(argStart));

const parsed = args.map(a => parseType(a));

// If there is a remainder after the generic (e.g. ".Method"),
// preserve it while shortening each dotted segment inside it.
function parseSuffix(rem: string): string {
if (!rem) return '';
let i = 0;
// accept leading dots/spaces
while (i < rem.length && (rem[i] === '.' || rem[i] === ' ')) i++;
if (i === 0) return rem; // unexpected format, return raw

const segments: string[] = [];
let segStart = i;
let depth = 0;
for (let j = i; j < rem.length; j++) {
const ch = rem[j];
if (ch === '<' || ch === '[') depth++; else if (ch === '>' || ch === ']') depth--;
else if (ch === '.' && depth === 0) {
segments.push(rem.substring(segStart, j));
segStart = j + 1;
}
}
if (segStart <= rem.length) segments.push(rem.substring(segStart));

const parsedSegs = segments.map(seg => parseType(seg));
return '.' + parsedSegs.join('.');
}

return `${trimIdentifier(main)}<${parsed.join(', ')}>${parseSuffix(remainder)}`;
}

return parseType(value);
}
Loading