Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions .changeset/source-section-field.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@hyperdx/common-utils": patch
"@hyperdx/api": patch
"@hyperdx/app": patch
---

feat: add an optional Section field to data sources

Sources can now carry an optional free-text Section label, set from the source
settings form. The value is persisted and returned by GET /api/v2/sources, so
external API consumers can read it. This lays the groundwork for grouping and
searching sources by section in the source selector.
24 changes: 24 additions & 0 deletions packages/api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2951,6 +2951,12 @@
"description": "Display name for the source.",
"example": "Logs"
},
"section": {
"type": "string",
"maxLength": 256,
"description": "Optional grouping label used to organize sources in the source selector. Sources that share a section value are displayed together.",
"example": "Billing"
},
"kind": {
"type": "string",
"enum": [
Expand Down Expand Up @@ -3139,6 +3145,12 @@
"description": "Display name for the source.",
"example": "Traces"
},
"section": {
"type": "string",
"maxLength": 256,
"description": "Optional grouping label used to organize sources in the source selector. Sources that share a section value are displayed together.",
"example": "Billing"
},
"kind": {
"type": "string",
"enum": [
Expand Down Expand Up @@ -3354,6 +3366,12 @@
"description": "Display name for the source.",
"example": "Metrics"
},
"section": {
"type": "string",
"maxLength": 256,
"description": "Optional grouping label used to organize sources in the source selector. Sources that share a section value are displayed together.",
"example": "Billing"
},
"kind": {
"type": "string",
"enum": [
Expand Down Expand Up @@ -3422,6 +3440,12 @@
"description": "Display name for the source.",
"example": "Sessions"
},
"section": {
"type": "string",
"maxLength": 256,
"description": "Optional grouping label used to organize sources in the source selector. Sources that share a section value are displayed together.",
"example": "Billing"
},
"kind": {
"type": "string",
"enum": [
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/models/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const sourceBaseSchema = new Schema<MongooseSourceBase>(
ref: 'Connection',
},
name: String,
section: String,
disabled: {
type: Boolean,
default: false,
Expand Down
48 changes: 48 additions & 0 deletions packages/api/src/routers/external-api/__tests__/sources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,54 @@ describe('External API v2 Sources', () => {
expect(response.body.data).toHaveLength(1);
expect(response.body.data[0].id).toBe(validSource._id.toString());
});

describe('section field', () => {
const SECTION = 'Control Plane Prod';

it('returns the section on a source that has one', async () => {
const logSource = await LogSource.create({
kind: SourceKind.Log,
team: team._id,
name: 'Sectioned Log Source',
section: SECTION,
from: {
databaseName: DEFAULT_DATABASE,
tableName: DEFAULT_LOGS_TABLE,
},
timestampValueExpression: 'Timestamp',
defaultTableSelectExpression: '*',
connection: connection._id,
});

const response = await authRequest('get', BASE_URL).expect(200);

expect(response.body.data).toHaveLength(1);
expect(response.body.data[0]).toMatchObject({
id: logSource._id.toString(),
section: SECTION,
});
});

it('omits the section on a source that has none', async () => {
await LogSource.create({
kind: SourceKind.Log,
team: team._id,
name: 'Unsectioned Log Source',
from: {
databaseName: DEFAULT_DATABASE,
tableName: DEFAULT_LOGS_TABLE,
},
timestampValueExpression: 'Timestamp',
defaultTableSelectExpression: '*',
connection: connection._id,
});

const response = await authRequest('get', BASE_URL).expect(200);

expect(response.body.data).toHaveLength(1);
expect(response.body.data[0]).not.toHaveProperty('section');
});
});
});

describe('backward compatibility with legacy flat-model documents', () => {
Expand Down
20 changes: 20 additions & 0 deletions packages/api/src/routers/external-api/v2/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@ function formatExternalSource(source: SourceDocument) {
* type: string
* description: Display name for the source.
* example: Logs
* section:
* type: string
* maxLength: 256
* description: Optional grouping label used to organize sources in the source selector. Sources that share a section value are displayed together.
* example: Billing
Comment thread
greptile-apps[bot] marked this conversation as resolved.
* kind:
* type: string
* enum: [log]
Expand Down Expand Up @@ -421,6 +426,11 @@ function formatExternalSource(source: SourceDocument) {
* type: string
* description: Display name for the source.
* example: Traces
* section:
* type: string
* maxLength: 256
* description: Optional grouping label used to organize sources in the source selector. Sources that share a section value are displayed together.
* example: Billing
* kind:
* type: string
* enum: [trace]
Expand Down Expand Up @@ -589,6 +599,11 @@ function formatExternalSource(source: SourceDocument) {
* type: string
* description: Display name for the source.
* example: Metrics
* section:
* type: string
* maxLength: 256
* description: Optional grouping label used to organize sources in the source selector. Sources that share a section value are displayed together.
* example: Billing
* kind:
* type: string
* enum: [metric]
Expand Down Expand Up @@ -641,6 +656,11 @@ function formatExternalSource(source: SourceDocument) {
* type: string
* description: Display name for the source.
* example: Sessions
* section:
* type: string
* maxLength: 256
* description: Optional grouping label used to organize sources in the source selector. Sources that share a section value are displayed together.
* example: Billing
* kind:
* type: string
* enum: [session]
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/components/Sources/SourceForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2447,6 +2447,14 @@ export function TableSourceForm({
rules={{ required: 'Name is required' }}
/>
</FormRow>
<FormRow label={'Section'}>
<InputControlled
control={control}
name="section"
placeholder="Optional group, e.g. Billing or Control Plane Prod"
maxLength={256}
/>
</FormRow>
Comment thread
greptile-apps[bot] marked this conversation as resolved.
<FormRow label={'Source Data Type'}>
<Controller
control={control}
Expand Down
1 change: 1 addition & 0 deletions packages/common-utils/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,7 @@ const RequiredTimestampColumnSchema = z
export const BaseSourceSchema = z.object({
id: z.string(),
name: z.string().min(1, 'Name is required'),
section: z.string().max(256).optional(),
kind: z.nativeEnum(SourceKind),
connection: z.string().min(1, 'Server Connection is required'),
from: z.object({
Expand Down
Loading