diff --git a/.changeset/twelve-beers-buy.md b/.changeset/twelve-beers-buy.md
new file mode 100644
index 000000000..9ec985094
--- /dev/null
+++ b/.changeset/twelve-beers-buy.md
@@ -0,0 +1,5 @@
+---
+"@hyperdx/app": patch
+---
+
+feat: Include displayed timestamp in default order by
diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx
index a13dc9a26..0b0462143 100644
--- a/packages/app/src/DBSearchPage.tsx
+++ b/packages/app/src/DBSearchPage.tsx
@@ -532,14 +532,26 @@ function useSearchedConfigToChartConfig({
function optimizeDefaultOrderBy(
timestampExpr: string,
+ displayedTimestampExpr: string | undefined,
sortingKey: string | undefined,
) {
const defaultModifier = 'DESC';
- const fallbackOrderByItems = [
- getFirstTimestampValueExpression(timestampExpr ?? ''),
- defaultModifier,
- ];
- const fallbackOrderBy = fallbackOrderByItems.join(' ');
+ const firstTimestampValueExpression =
+ getFirstTimestampValueExpression(timestampExpr ?? '') ?? '';
+ const defaultOrderByItems = [firstTimestampValueExpression];
+ const trimmedDisplayedTimestampExpr = displayedTimestampExpr?.trim();
+
+ if (
+ trimmedDisplayedTimestampExpr &&
+ trimmedDisplayedTimestampExpr !== firstTimestampValueExpression
+ ) {
+ defaultOrderByItems.push(trimmedDisplayedTimestampExpr);
+ }
+
+ const fallbackOrderBy =
+ defaultOrderByItems.length > 1
+ ? `(${defaultOrderByItems.join(', ')}) ${defaultModifier}`
+ : `${defaultOrderByItems[0]} ${defaultModifier}`;
if (!sortingKey) return fallbackOrderBy;
@@ -547,13 +559,17 @@ function optimizeDefaultOrderBy(
const sortKeys = splitAndTrimWithBracket(sortingKey);
for (let i = 0; i < sortKeys.length; i++) {
const sortKey = sortKeys[i];
- if (sortKey.includes('toStartOf') && sortKey.includes(timestampExpr)) {
+ if (
+ sortKey.includes('toStartOf') &&
+ sortKey.includes(firstTimestampValueExpression)
+ ) {
orderByArr.push(sortKey);
} else if (
- sortKey === timestampExpr ||
+ sortKey === firstTimestampValueExpression ||
(sortKey.startsWith('toUnixTimestamp') &&
- sortKey.includes(timestampExpr)) ||
- (sortKey.startsWith('toDateTime') && sortKey.includes(timestampExpr))
+ sortKey.includes(firstTimestampValueExpression)) ||
+ (sortKey.startsWith('toDateTime') &&
+ sortKey.includes(firstTimestampValueExpression))
) {
if (orderByArr.length === 0) {
// fallback if the first sort key is the timestamp sort key
@@ -562,6 +578,8 @@ function optimizeDefaultOrderBy(
orderByArr.push(sortKey);
break;
}
+ } else if (sortKey === trimmedDisplayedTimestampExpr) {
+ orderByArr.push(sortKey);
}
}
@@ -570,7 +588,16 @@ function optimizeDefaultOrderBy(
return fallbackOrderBy;
}
- return `(${orderByArr.join(', ')}) ${defaultModifier}`;
+ if (
+ trimmedDisplayedTimestampExpr &&
+ !orderByArr.includes(trimmedDisplayedTimestampExpr)
+ ) {
+ orderByArr.push(trimmedDisplayedTimestampExpr);
+ }
+
+ return orderByArr.length > 1
+ ? `(${orderByArr.join(', ')}) ${defaultModifier}`
+ : `${orderByArr[0]} ${defaultModifier}`;
}
export function useDefaultOrderBy(sourceID: string | undefined | null) {
@@ -582,6 +609,7 @@ export function useDefaultOrderBy(sourceID: string | undefined | null) {
() =>
optimizeDefaultOrderBy(
source?.timestampValueExpression ?? '',
+ source?.displayedTimestampValueExpression,
tableMetadata?.sorting_key,
),
[source, tableMetadata],
diff --git a/packages/app/src/__tests__/DBSearchPage.test.tsx b/packages/app/src/__tests__/DBSearchPage.test.tsx
index 9e6e5475e..f29009797 100644
--- a/packages/app/src/__tests__/DBSearchPage.test.tsx
+++ b/packages/app/src/__tests__/DBSearchPage.test.tsx
@@ -17,82 +17,157 @@ describe('useDefaultOrderBy', () => {
describe('optimizeOrderBy function', () => {
describe('should handle', () => {
- const mockSource = {
- timestampValueExpression: 'Timestamp',
- };
-
const testCases = [
{
- input: undefined,
+ sortingKey: undefined,
expected: 'Timestamp DESC',
},
{
- input: '',
+ sortingKey: '',
expected: 'Timestamp DESC',
},
{
// Traces Table
- input: 'ServiceName, SpanName, toDateTime(Timestamp)',
+ sortingKey: 'ServiceName, SpanName, toDateTime(Timestamp)',
expected: 'Timestamp DESC',
},
{
// Optimized Traces Table
- input:
+ sortingKey:
'toStartOfHour(Timestamp), ServiceName, SpanName, toDateTime(Timestamp)',
expected: '(toStartOfHour(Timestamp), toDateTime(Timestamp)) DESC',
},
{
// Unsupported for now as it's not a great sort key, want to just
// use default behavior for this
- input: 'toDateTime(Timestamp), ServiceName, SpanName, Timestamp',
+ sortingKey: 'toDateTime(Timestamp), ServiceName, SpanName, Timestamp',
expected: 'Timestamp DESC',
},
{
// Unsupported prefix sort key
- input: 'toDateTime(Timestamp), ServiceName, SpanName',
+ sortingKey: 'toDateTime(Timestamp), ServiceName, SpanName',
expected: 'Timestamp DESC',
},
{
// Inverted sort key order, we should not try to optimize this
- input:
+ sortingKey:
'ServiceName, toDateTime(Timestamp), SeverityText, toStartOfHour(Timestamp)',
expected: 'Timestamp DESC',
},
{
- input: 'toStartOfHour(Timestamp), other_column, Timestamp',
+ sortingKey: 'toStartOfHour(Timestamp), other_column, Timestamp',
expected: '(toStartOfHour(Timestamp), Timestamp) DESC',
},
{
- input: 'Timestamp, other_column',
+ sortingKey: 'Timestamp, other_column',
expected: 'Timestamp DESC',
},
{
- input: 'user_id, toStartOfHour(Timestamp), status, Timestamp',
+ sortingKey: 'user_id, toStartOfHour(Timestamp), status, Timestamp',
expected: '(toStartOfHour(Timestamp), Timestamp) DESC',
},
{
- input:
+ sortingKey:
'toStartOfMinute(Timestamp), user_id, status, toUnixTimestamp(Timestamp)',
expected:
'(toStartOfMinute(Timestamp), toUnixTimestamp(Timestamp)) DESC',
},
{
// test variation of toUnixTimestamp
- input:
+ sortingKey:
'toStartOfMinute(Timestamp), user_id, status, toUnixTimestamp64Nano(Timestamp)',
expected:
'(toStartOfMinute(Timestamp), toUnixTimestamp64Nano(Timestamp)) DESC',
},
{
- input:
+ sortingKey:
'toUnixTimestamp(toStartOfMinute(Timestamp)), user_id, status, Timestamp',
expected:
'(toUnixTimestamp(toStartOfMinute(Timestamp)), Timestamp) DESC',
},
+ {
+ sortingKey: 'toStartOfMinute(Timestamp), user_id, status, Timestamp',
+ timestampValueExpression: 'Timestamp, toStartOfMinute(Timestamp)',
+ expected: '(toStartOfMinute(Timestamp), Timestamp) DESC',
+ },
+ {
+ sortingKey: 'Timestamp',
+ displayedTimestampValueExpression: 'Timestamp64',
+ expected: '(Timestamp, Timestamp64) DESC',
+ },
+ {
+ sortingKey: 'Timestamp',
+ displayedTimestampValueExpression: 'Timestamp64 ',
+ expected: '(Timestamp, Timestamp64) DESC',
+ },
+ {
+ sortingKey: 'Timestamp',
+ displayedTimestampValueExpression: 'Timestamp',
+ expected: 'Timestamp DESC',
+ },
+ {
+ sortingKey: 'Timestamp',
+ displayedTimestampValueExpression: '',
+ expected: 'Timestamp DESC',
+ },
+ {
+ sortingKey: 'Timestamp, ServiceName, Timestamp64',
+ displayedTimestampValueExpression: 'Timestamp64',
+ expected: '(Timestamp, Timestamp64) DESC',
+ },
+ {
+ sortingKey:
+ 'toStartOfMinute(Timestamp), Timestamp, ServiceName, Timestamp64',
+ displayedTimestampValueExpression: 'Timestamp64',
+ expected: '(toStartOfMinute(Timestamp), Timestamp, Timestamp64) DESC',
+ },
+ {
+ sortingKey:
+ 'toStartOfMinute(Timestamp), Timestamp64, ServiceName, Timestamp',
+ displayedTimestampValueExpression: 'Timestamp64',
+ expected: '(toStartOfMinute(Timestamp), Timestamp64, Timestamp) DESC',
+ },
+ {
+ sortingKey: 'SomeOtherTimeColumn',
+ displayedTimestampValueExpression: 'Timestamp64',
+ expected: '(Timestamp, Timestamp64) DESC',
+ },
+ {
+ sortingKey: '',
+ displayedTimestampValueExpression: 'Timestamp64',
+ expected: '(Timestamp, Timestamp64) DESC',
+ },
+ {
+ sortingKey: 'ServiceName, TimestampTime, Timestamp',
+ timestampValueExpression: 'TimestampTime, Timestamp',
+ displayedTimestampValueExpression: 'Timestamp',
+ expected: '(TimestampTime, Timestamp) DESC',
+ },
+ {
+ sortingKey: 'ServiceName, TimestampTime, Timestamp',
+ timestampValueExpression: 'Timestamp, TimestampTime',
+ displayedTimestampValueExpression: 'Timestamp',
+ expected: 'Timestamp DESC',
+ },
+ {
+ sortingKey: '',
+ timestampValueExpression: 'Timestamp, TimestampTime',
+ displayedTimestampValueExpression: '',
+ expected: 'Timestamp DESC',
+ },
];
for (const testCase of testCases) {
- it(`${testCase.input}`, () => {
- const mockTableMetadata = { sorting_key: testCase.input };
+ it(`${testCase.sortingKey}`, () => {
+ const mockSource = {
+ timestampValueExpression:
+ testCase.timestampValueExpression || 'Timestamp',
+ displayedTimestampValueExpression:
+ testCase.displayedTimestampValueExpression,
+ };
+
+ const mockTableMetadata = {
+ sorting_key: testCase.sortingKey,
+ };
jest.spyOn(sourceModule, 'useSource').mockReturnValue({
data: mockSource,
diff --git a/packages/app/src/components/SourceForm.tsx b/packages/app/src/components/SourceForm.tsx
index 98fe39a22..184aca33e 100644
--- a/packages/app/src/components/SourceForm.tsx
+++ b/packages/app/src/components/SourceForm.tsx
@@ -285,7 +285,7 @@ export function LogTableModelForm({ control, watch }: TableModelProps) {
+
+
+
);
}