Skip to content

Commit b8f1f1b

Browse files
Merge pull request #745 from bruin-data/improve/show-interval-modifiers-feedback
Show how the dates changes if interval modifiers are applied
2 parents 60e1b8f + a19a61b commit b8f1f1b

9 files changed

Lines changed: 352 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Changelog
2+
## [0.79.5] - [2026-03-23]
3+
- Added interval modifiers preview tooltip: hover over the "Interval-modifiers" checkbox to see how start/end dates will be modified.
4+
25
## [0.79.4] - [2026-03-23]
36
- Fixed "Fill from DB" to use the selected environment instead of the default environment.
47
- Fixed start date input to remain enabled when "Full Refresh" is selected.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ Bruin is a unified analytics platform that enables data professionals to work en
7070
## Release Notes
7171

7272
### Recent Update
73+
- **0.79.5**: Added interval modifiers preview tooltip on the checkbox.
7374
- **0.79.4**: Fixed "Fill from DB" to use selected environment; fixed start date input on full refresh.
7475
- **0.79.3**: Added ability to select and run SQL text directly in Query Preview.
7576
- **0.79.2**: Added cancel button for export query operations, allowing users to stop long-running exports.
7677
- **0.79.1**: Fixed SQL Editor syntax highlighting.
77-
- **0.79.0**: Added copy results as CSV, TSV, or JSON to the Query Preview.
7878

7979
For a full changelog, see Bruin Extension [Changelog](https://github.com/bruin-data/bruin-vscode/blob/main/CHANGELOG.md).

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "bruin",
33
"displayName": "Bruin",
44
"description": "Manage your Bruin data assets from within VS Code.",
5-
"version": "0.79.4",
5+
"version": "0.79.5",
66
"engines": {
77
"vscode": "^1.87.0"
88
},

webview-ui/src/App.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ const tabs = ref([
722722
environments: environmentsList.value,
723723
selectedEnvironment: selectedEnvironment.value,
724724
hasIntervalModifiers: hasIntervalModifiers.value,
725+
intervalModifiers: intervalModifiers.value,
725726
startDate: startDate.value,
726727
parameters: ingestrParameters.value,
727728
columns: columns.value,

webview-ui/src/components/asset/AssetDetails.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
:environments="environments"
8585
:selectedEnvironment="selectedEnvironment"
8686
:hasIntervalModifiers="hasIntervalModifiers"
87+
:intervalModifiers="props.intervalModifiers"
8788
:assetType="props.type"
8889
:parameters="parameters"
8990
:columns="props.columns || []"
@@ -119,6 +120,7 @@ const props = defineProps<{
119120
environments: string[];
120121
selectedEnvironment: string;
121122
hasIntervalModifiers: boolean;
123+
intervalModifiers?: { start?: string; end?: string };
122124
parameters?: any;
123125
columns?: any[];
124126
tags?: string[];

webview-ui/src/components/asset/AssetGeneral.vue

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
</div>
2525
</div>
2626
</div>
27-
</div>
27+
</div>
2828
</div>
2929

3030
<!-- Conditional rendering of CheckboxGroup -->
3131
<div v-if="showCheckboxGroup">
32-
<CheckboxGroup :checkboxItems="checkboxItems" label="Options" />
32+
<CheckboxGroup :checkboxItems="checkboxItems" label="Options" :tooltips="checkboxTooltips" />
3333
<!-- Tag Filter Controls (subtle) -->
3434
<div class="mt-2 flex items-center gap-2" ref="tagFilterContainer">
3535
<div class="flex items-center gap-[1px] relative">
@@ -411,6 +411,11 @@ import type { FormattedErrorMessage } from "@/types";
411411
import { Transition } from "vue";
412412
import RudderStackService from "@/services/RudderStackService";
413413
import { useConnectionsStore, usePipelineRunStore } from "@/store/bruinStore";
414+
import {
415+
parseIntervalModifier,
416+
applyModifierToDate,
417+
formatModifierForDisplay,
418+
} from "@/utilities/intervalModifiers";
414419
415420
/**
416421
* Define component props
@@ -421,6 +426,7 @@ const props = defineProps<{
421426
environments: string[];
422427
selectedEnvironment: string;
423428
hasIntervalModifiers: boolean;
429+
intervalModifiers?: { start?: string; end?: string };
424430
assetType?: string;
425431
parameters: any;
426432
columns: any[];
@@ -981,6 +987,77 @@ watch(
981987
{ immediate: true }
982988
);
983989
990+
// Computed property for modified dates preview
991+
const modifiedDatesPreview = computed(() => {
992+
if (!props.hasIntervalModifiers || !props.intervalModifiers) {
993+
return null;
994+
}
995+
996+
const startModifier = parseIntervalModifier(props.intervalModifiers.start);
997+
const endModifier = parseIntervalModifier(props.intervalModifiers.end);
998+
999+
if (!startModifier && !endModifier) {
1000+
return null;
1001+
}
1002+
1003+
const result: {
1004+
startModified: string | null;
1005+
startModifierText: string;
1006+
endModified: string | null;
1007+
endModifierText: string;
1008+
} = {
1009+
startModified: null,
1010+
startModifierText: '',
1011+
endModified: null,
1012+
endModifierText: '',
1013+
};
1014+
1015+
if (startModifier) {
1016+
const modifiedStart = applyModifierToDate(startDate.value, startModifier);
1017+
result.startModified = modifiedStart.toFormat('yyyy-MM-dd HH:mm');
1018+
result.startModifierText = formatModifierForDisplay(props.intervalModifiers.start);
1019+
}
1020+
1021+
if (endModifier) {
1022+
const modifiedEnd = applyModifierToDate(endDate.value, endModifier);
1023+
result.endModified = modifiedEnd.toFormat('yyyy-MM-dd HH:mm');
1024+
result.endModifierText = formatModifierForDisplay(props.intervalModifiers.end);
1025+
}
1026+
1027+
return result;
1028+
});
1029+
1030+
// Check if interval modifiers checkbox is checked
1031+
const isIntervalModifiersChecked = computed(() => {
1032+
return checkboxItems.value.find((item) => item.name === "Interval-modifiers")?.checked || false;
1033+
});
1034+
1035+
// Generate tooltip text for interval modifiers checkbox
1036+
const intervalModifiersTooltip = computed(() => {
1037+
const preview = modifiedDatesPreview.value;
1038+
if (!preview) return '';
1039+
1040+
const lines: string[] = [];
1041+
if (preview.startModified) {
1042+
lines.push(`Start: ${preview.startModified} (${preview.startModifierText})`);
1043+
}
1044+
if (preview.endModified) {
1045+
lines.push(`End: ${preview.endModified} (${preview.endModifierText})`);
1046+
}
1047+
1048+
if (lines.length === 0) return '';
1049+
return lines.join('\n');
1050+
});
1051+
1052+
// Tooltips for checkboxes
1053+
const checkboxTooltips = computed(() => {
1054+
const tooltips: Record<string, string> = {};
1055+
if (intervalModifiersTooltip.value) {
1056+
tooltips['Interval-modifiers'] = intervalModifiersTooltip.value;
1057+
}
1058+
return tooltips;
1059+
});
1060+
9841061
function getCheckboxChangePayload() {
9851062
const effectiveStartDate = startDate.value;
9861063

webview-ui/src/components/ui/checkbox-group/CheckboxGroup.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:key="index"
77
@change="handleCheckboxChange(item, $event)"
88
:checked="item.checked"
9+
:title="getTooltip(item.name)"
910
>
1011
{{ item.name }}
1112
</vscode-checkbox>
@@ -20,11 +21,16 @@ import type { CheckboxItems } from "@/types";
2021
const props = defineProps<{
2122
checkboxItems: CheckboxItems[];
2223
label: string;
24+
tooltips?: Record<string, string>;
2325
}>();
2426
2527
const expanded = ref(true);
2628
2729
function handleCheckboxChange(item: any, event: any) {
2830
item.checked = event.target.checked;
2931
}
32+
33+
function getTooltip(name: string): string {
34+
return props.tooltips?.[name] || '';
35+
}
3036
</script>
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { describe, test, expect } from "vitest";
2+
import {
3+
parseIntervalModifier,
4+
applyModifierToDate,
5+
formatModifierForDisplay,
6+
} from "../utilities/intervalModifiers";
7+
8+
describe("parseIntervalModifier", () => {
9+
test("returns null for undefined/null", () => {
10+
expect(parseIntervalModifier(undefined)).toBeNull();
11+
expect(parseIntervalModifier(null)).toBeNull();
12+
});
13+
14+
test("returns null for empty string", () => {
15+
expect(parseIntervalModifier("")).toBeNull();
16+
});
17+
18+
test("parses days", () => {
19+
expect(parseIntervalModifier("-13d")).toEqual({ value: -13, unit: "days" });
20+
expect(parseIntervalModifier("5d")).toEqual({ value: 5, unit: "days" });
21+
});
22+
23+
test("parses hours", () => {
24+
expect(parseIntervalModifier("-2h")).toEqual({ value: -2, unit: "hours" });
25+
expect(parseIntervalModifier("24h")).toEqual({ value: 24, unit: "hours" });
26+
});
27+
28+
test("parses minutes", () => {
29+
expect(parseIntervalModifier("-30m")).toEqual({ value: -30, unit: "minutes" });
30+
expect(parseIntervalModifier("15m")).toEqual({ value: 15, unit: "minutes" });
31+
});
32+
33+
test("parses seconds", () => {
34+
expect(parseIntervalModifier("-60s")).toEqual({ value: -60, unit: "seconds" });
35+
expect(parseIntervalModifier("30s")).toEqual({ value: 30, unit: "seconds" });
36+
});
37+
38+
test("parses months (uppercase M)", () => {
39+
expect(parseIntervalModifier("-1M")).toEqual({ value: -1, unit: "months" });
40+
expect(parseIntervalModifier("3M")).toEqual({ value: 3, unit: "months" });
41+
});
42+
43+
test("parses milliseconds (two-char suffix)", () => {
44+
expect(parseIntervalModifier("500ms")).toEqual({ value: 500, unit: "milliseconds" });
45+
expect(parseIntervalModifier("-100ms")).toEqual({ value: -100, unit: "milliseconds" });
46+
});
47+
48+
test("parses nanoseconds (two-char suffix)", () => {
49+
expect(parseIntervalModifier("1000ns")).toEqual({ value: 1000, unit: "nanoseconds" });
50+
expect(parseIntervalModifier("-500ns")).toEqual({ value: -500, unit: "nanoseconds" });
51+
});
52+
53+
test("returns null for invalid format", () => {
54+
expect(parseIntervalModifier("invalid")).toBeNull();
55+
expect(parseIntervalModifier("13")).toBeNull();
56+
expect(parseIntervalModifier("d13")).toBeNull();
57+
expect(parseIntervalModifier("13x")).toBeNull();
58+
});
59+
60+
test("returns null for non-string input", () => {
61+
// @ts-expect-error Testing invalid input
62+
expect(parseIntervalModifier(123)).toBeNull();
63+
// @ts-expect-error Testing invalid input
64+
expect(parseIntervalModifier({ days: -13 })).toBeNull();
65+
});
66+
});
67+
68+
describe("applyModifierToDate", () => {
69+
const baseDate = "2025-03-15T12:00:00.000Z";
70+
71+
test("applies days modifier", () => {
72+
const result = applyModifierToDate(baseDate, { value: -13, unit: "days" });
73+
expect(result.toISO()).toBe("2025-03-02T12:00:00.000Z");
74+
});
75+
76+
test("applies positive days modifier", () => {
77+
const result = applyModifierToDate(baseDate, { value: 5, unit: "days" });
78+
expect(result.toISO()).toBe("2025-03-20T12:00:00.000Z");
79+
});
80+
81+
test("applies hours modifier", () => {
82+
const result = applyModifierToDate(baseDate, { value: -2, unit: "hours" });
83+
expect(result.toISO()).toBe("2025-03-15T10:00:00.000Z");
84+
});
85+
86+
test("applies months modifier", () => {
87+
const result = applyModifierToDate(baseDate, { value: -1, unit: "months" });
88+
expect(result.toISO()).toBe("2025-02-15T12:00:00.000Z");
89+
});
90+
91+
test("applies minutes modifier", () => {
92+
const result = applyModifierToDate(baseDate, { value: -30, unit: "minutes" });
93+
expect(result.toISO()).toBe("2025-03-15T11:30:00.000Z");
94+
});
95+
96+
test("applies seconds modifier", () => {
97+
const result = applyModifierToDate(baseDate, { value: -60, unit: "seconds" });
98+
expect(result.toISO()).toBe("2025-03-15T11:59:00.000Z");
99+
});
100+
101+
test("applies milliseconds modifier", () => {
102+
const result = applyModifierToDate(baseDate, { value: 500, unit: "milliseconds" });
103+
expect(result.toISO()).toBe("2025-03-15T12:00:00.500Z");
104+
});
105+
});
106+
107+
describe("formatModifierForDisplay", () => {
108+
test("returns empty string for null/undefined", () => {
109+
expect(formatModifierForDisplay(undefined)).toBe("");
110+
expect(formatModifierForDisplay(null)).toBe("");
111+
});
112+
113+
test("formats negative days", () => {
114+
expect(formatModifierForDisplay("-13d")).toBe("-13 days");
115+
});
116+
117+
test("formats positive days with plus sign", () => {
118+
expect(formatModifierForDisplay("5d")).toBe("+5 days");
119+
});
120+
121+
test("formats singular day", () => {
122+
expect(formatModifierForDisplay("1d")).toBe("+1 day");
123+
expect(formatModifierForDisplay("-1d")).toBe("-1 day");
124+
});
125+
126+
test("formats hours", () => {
127+
expect(formatModifierForDisplay("-2h")).toBe("-2 hours");
128+
expect(formatModifierForDisplay("1h")).toBe("+1 hour");
129+
});
130+
131+
test("formats minutes", () => {
132+
expect(formatModifierForDisplay("-30m")).toBe("-30 minutes");
133+
expect(formatModifierForDisplay("1m")).toBe("+1 minute");
134+
});
135+
136+
test("formats months", () => {
137+
expect(formatModifierForDisplay("-1M")).toBe("-1 month");
138+
expect(formatModifierForDisplay("3M")).toBe("+3 months");
139+
});
140+
141+
test("formats milliseconds", () => {
142+
expect(formatModifierForDisplay("500ms")).toBe("+500 milliseconds");
143+
expect(formatModifierForDisplay("-100ms")).toBe("-100 milliseconds");
144+
});
145+
146+
test("formats nanoseconds", () => {
147+
expect(formatModifierForDisplay("1000ns")).toBe("+1000 nanoseconds");
148+
expect(formatModifierForDisplay("-1ns")).toBe("-1 nanosecond");
149+
});
150+
});

0 commit comments

Comments
 (0)