diff --git a/tests/ui-testing/pages/generalPages/streams.js b/tests/ui-testing/pages/generalPages/streams.js
index 32715226b7..10cf643ff0 100644
--- a/tests/ui-testing/pages/generalPages/streams.js
+++ b/tests/ui-testing/pages/generalPages/streams.js
@@ -16,6 +16,7 @@ export default class StreamSettingsPage {
'[data-test="schema-update-settings-button"]'
);
this.closeButton = page.locator('[data-test="schema-cancel-button"]');
+ this.configurationTab = page.getByRole('tab', { name: 'Configuration' })
}
async updateStreamMaxQueryRange(streamName, newValue) {
@@ -35,9 +36,11 @@ export default class StreamSettingsPage {
// Wait for stream details to appear and click
- await this.streamDetailButton.waitFor({ state: "visible", timeout: 5000 });
+ await this.streamDetailButton.waitFor({ state: "visible", timeout: 2000 });
await this.streamDetailButton.click();
-
+ //before clicking for max query range input we need to go to configuration tab due to schema UI layout change
+ await this.configurationTab.waitFor({ state: "visible", timeout: 2000 });
+ await this.configurationTab.click();
// Wait and update max query range input
await this.maxQueryInput.waitFor({ state: "visible", timeout: 15000 });
await this.maxQueryInput.click();
diff --git a/tests/ui-testing/playwright-tests/Logs/unflattened.spec.js b/tests/ui-testing/playwright-tests/Logs/unflattened.spec.js
index 58b9dd7e35..78a5e462d9 100644
--- a/tests/ui-testing/playwright-tests/Logs/unflattened.spec.js
+++ b/tests/ui-testing/playwright-tests/Logs/unflattened.spec.js
@@ -131,7 +131,10 @@ test.describe("Unflattened testcases", () => {
await pageManager.unflattenedPage.streamDetailButton.click();
// Strategic 500ms wait for operation completion - this is functionally necessary
await page.waitForTimeout(500);
-
+
+ //before toggling we need to make sure that we shift to configuration tab
+ await page.getByRole('tab', { name: 'Configuration' }).waitFor({ state: "visible", timeout: 2000 });
+ await page.getByRole('tab', { name: 'Configuration' }).click();
// Toggle 'Store Original Data' and update schema
await pageManager.unflattenedPage.storeOriginalDataToggle.waitFor(); // Wait for the toggle to be visible
await pageManager.unflattenedPage.storeOriginalDataToggle.click();
@@ -206,7 +209,9 @@ test.describe("Unflattened testcases", () => {
await pageManager.unflattenedPage.streamDetailButton.click();
// Strategic 500ms wait for operation completion - this is functionally necessary
await page.waitForTimeout(500);
-
+ //before toggling we need to make sure that we shift to configuration tab
+ await page.getByRole('tab', { name: 'Configuration' }).waitFor({ state: "visible", timeout: 2000 });
+ await page.getByRole('tab', { name: 'Configuration' }).click();
// Toggle 'Store Original Data' and update schema
await pageManager.unflattenedPage.storeOriginalDataToggle.waitFor();
await pageManager.unflattenedPage.storeOriginalDataToggle.click();
@@ -318,7 +323,9 @@ test.describe("Unflattened testcases", () => {
await pageManager.unflattenedPage.streamDetailButton.click();
// Strategic 500ms wait for operation completion - this is functionally necessary
await page.waitForTimeout(500);
-
+ //before toggling we need to make sure that we shift to configuration tab
+ await page.getByRole('tab', { name: 'Configuration' }).waitFor({ state: "visible", timeout: 2000 });
+ await page.getByRole('tab', { name: 'Configuration' }).click();
await pageManager.unflattenedPage.storeOriginalDataToggle.waitFor();
await pageManager.unflattenedPage.storeOriginalDataToggle.click();
diff --git a/web/src/assets/images/streams/config.svg b/web/src/assets/images/streams/config.svg
new file mode 100644
index 0000000000..f2bc58c100
--- /dev/null
+++ b/web/src/assets/images/streams/config.svg
@@ -0,0 +1,31 @@
+
+
+
+
\ No newline at end of file
diff --git a/web/src/assets/images/streams/config_light.svg b/web/src/assets/images/streams/config_light.svg
new file mode 100644
index 0000000000..85ea797719
--- /dev/null
+++ b/web/src/assets/images/streams/config_light.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/web/src/assets/images/streams/timeline.svg b/web/src/assets/images/streams/timeline.svg
new file mode 100644
index 0000000000..64f83bb96b
--- /dev/null
+++ b/web/src/assets/images/streams/timeline.svg
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/web/src/assets/images/streams/timeline_light.svg b/web/src/assets/images/streams/timeline_light.svg
new file mode 100644
index 0000000000..fd9b667fc8
--- /dev/null
+++ b/web/src/assets/images/streams/timeline_light.svg
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/web/src/components/logstream/schema.vue b/web/src/components/logstream/schema.vue
index 7d27289c51..8dfd62f3f7 100644
--- a/web/src/components/logstream/schema.vue
+++ b/web/src/components/logstream/schema.vue
@@ -24,10 +24,54 @@ along with this program. If not, see .
{{ t("logStream.schemaHeader") }}
+
+
+ {{ indexData.name }}
+
+ {{ indexData.name }}
+
+
+
+
![Timeline Icon]()
+
+
+ UTC
+
+
+ {{ indexData.stats.doc_time_min }} → {{ indexData.stats.doc_time_max }}
+
+
+
@@ -40,6 +84,7 @@ along with this program. If not, see
.
+
.
>
-
-
-
-
- {{ t("alerts.stream_name") }}
- {{ indexData.name }}
-
+
+
+
+
+
- {{ t("logStream.docsCount") }}
-
- {{
- parseInt(indexData.stats.doc_num).toLocaleString("en-US")
- }}
-
-
-
- {{ t("logStream.storageSize") }}
-
- {{ formatSizeFromMB(indexData.stats.storage_size) }}
-
-
- {{ t("logStream.compressedSize") }}
-
- {{ formatSizeFromMB(indexData.stats.compressed_size) }}
-
-
-
-
- {{ t("logStream.indexSize") }}
-
- {{ formatSizeFromMB(indexData.stats.index_size) }}
-
-
-
- Start Time:
- {{ indexData.stats.doc_time_min }}
-
-
-
- End Time:
- {{ indexData.stats.doc_time_max }}
-
-
-
-
-
-
-
-
-
-
-
- (Global retention is
- {{ store.state.zoConfig.data_retention_days }} days)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- (Global is
- {{ store.state.zoConfig.ingest_flatten_level || 3 }})
-
-
-
-
-
-
-
-
-
-
-
-
- Retention period must be at least 1 day
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
- Other fields show only the schema fields that existed
- before the stream was configured to use a user-defined
- schema.
-
-
-
-
-
-
-
-
-
-
-
-
- Add Field(s)
-
-
-
-
-
-
-
-
-
-
-
Add Field(s)
-
-
+ Events
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ col.label }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ props.row.name }}
-
-
-
- {{ t('logStream.envQuickModeMsg') }}
-
-
-
-
-
- {{ props.row.type }}
-
-
-
-
-
-
-
-
-
-
- updateIndexType(props, val)"
- >
-
-
-
-
- {{ scope.opt.label }}
-
-
-
- This is a predefined environment setting and cannot be changed.
-
-
-
-
-
-
-
-
-
- {{ patternAssociations[props.row.name]?.length ? `View ${patternAssociations[props.row.name]?.length} Patterns` : 'Add Pattern' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Additional
- {{ store.state.zoConfig.extended_data_retention_days }} days of
- extension will be applied to the selected date ranges
-
-
-
-
-
Select Date
-
+
+ {{ parseInt(indexData.stats.doc_num).toLocaleString("en-US") }}
+
-
(UTC Timezone)
+
+
+
+
+
+
+ {{ formatSizeFromMB(indexData.stats.storage_size) }}
+
+
+
+
+
+
+
+
+ {{ formatSizeFromMB(indexData.stats.compressed_size) }}
+
+
+
+
+
+
+
+
+ {{ formatSizeFromMB(indexData.stats.index_size) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Other fields show only the schema fields that existed
+ before the stream was configured to use a user-defined
+ schema.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ col.label }}
+
+
+
+
.
-
-
+
+
+
+
+
+
+ {{ props.row.name }}
+
+ {{ props.row.name }}
+
+
+
+
+
+ {{ t('logStream.envQuickModeMsg') }}
+
+
+
+
+
+
+
+
+ {{ props.row.type }}
+
+
+
+
+
+
+
+
+
+
+
+
+ updateIndexType(props, val)"
+ >
+
+
+
+
+
+
+
+ {{ scope.opt.label }}
+
+
+
+ This is a predefined environment setting and cannot be changed.
+
+
+
+
+ {{ streamIndexType.filter(opt => props.row.index_type.includes(opt.value)).map(opt => opt.label).join(', ') }}
+
+
+
+
+
+
+
+ {{ patternAssociations[props.row.name]?.length ? `View ${patternAssociations[props.row.name]?.length} Patterns` : 'Add Pattern' }}
+
+
+
+
+
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Global retention is {{ store.state.zoConfig.data_retention_days }} days
+
+
+
+
+
+ Retention period must be at least 1 day
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+ Additional
+ {{ store.state.zoConfig.extended_data_retention_days }} days of
+ extension will be applied to the selected date ranges
+
+
+
+
+ Select Date
+
+
+
(UTC Timezone)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- {{ selectedFields.length }} fields
- selected
-
-
-
-
-
- {{
- activeTab === "schemaFields"
- ? t("logStream.removeSchemaField")
- : t("logStream.addSchemaField")
- }}
-
-
-
-
- {{ t("logStream.delete") }}
-
-
-
-
-
-
+
+
+ {{ selectedFields.length }} fields
+ selected
+
+
+
+
+
+ {{
+ activeTab === "schemaFields"
+ ? t("logStream.removeSchemaField")
+ : t("logStream.addSchemaField")
+ }}
+
+
+
+
+ {{ t("logStream.delete") }}
+
+
+
+
+
+
+
+
-
@@ -750,6 +997,7 @@ import { useStore } from "vuex";
import { useQuasar, date, format } from "quasar";
import streamService from "../../services/stream";
import segment from "../../services/segment_analytics";
+import "../../styles/schema.scss";
import {
formatSizeFromMB,
getImageURL,
@@ -817,6 +1065,7 @@ export default defineComponent({
const isCloud = config.isCloud;
const dataRetentionDays = ref(0);
const storeOriginalData = ref(false);
+ const enableDistinctFields = ref(false);
const maxQueryRange = ref(0);
const flattenLevel = ref(null);
const confirmQueryModeChangeDialog = ref(false);
@@ -843,12 +1092,11 @@ export default defineComponent({
const redDaysList = ref([]);
const resultTotal = ref(0);
const perPageOptions: any = [
- { label: "5", value: 5 },
- { label: "10", value: 10 },
{ label: "20", value: 20 },
{ label: "50", value: 50 },
{ label: "100", value: 100 },
- { label: "All", value: 0 },
+ { label: "250", value: 250 },
+ { label: "500", value: 500 },
];
const changePagination = (val: { label: string; value: any }) => {
@@ -938,6 +1186,7 @@ export default defineComponent({
dataRetentionDays.value = store.state.zoConfig.data_retention_days || 0;
maxQueryRange.value = 0;
storeOriginalData.value = false;
+ enableDistinctFields.value = false;
approxPartition.value = false;
});
@@ -1114,15 +1363,19 @@ export default defineComponent({
indexData.value.schema = streamResponse.schema || [];
indexData.value.stats = JSON.parse(JSON.stringify(streamResponse.stats));
+ indexData.value.stats.original_doc_time_max = streamResponse.stats.doc_time_max;
+ indexData.value.stats.original_doc_time_min = streamResponse.stats.doc_time_min;
+
indexData.value.stats.doc_time_max = date.formatDate(
parseInt(streamResponse.stats.doc_time_max) / 1000,
- "YYYY-MM-DD THH:mm:ss:SS Z",
+ "YYYY-MM-DDTHH:mm:ss:SS",
);
indexData.value.stats.doc_time_min = date.formatDate(
parseInt(streamResponse.stats.doc_time_min) / 1000,
- "YYYY-MM-DD THH:mm:ss:SS Z",
+ "YYYY-MM-DDTHH:mm:ss:SS",
);
+
indexData.value.defined_schema_fields =
streamResponse.settings.defined_schema_fields || [];
@@ -1136,6 +1389,7 @@ export default defineComponent({
flattenLevel.value = streamResponse.settings.flatten_level || null;
storeOriginalData.value =
streamResponse.settings.store_original_data || false;
+ enableDistinctFields.value = streamResponse.settings.enable_distinct_fields || false;
approxPartition.value = streamResponse.settings.approx_partition || false;
if (!streamResponse.schema) {
@@ -1232,6 +1486,7 @@ export default defineComponent({
}
settings["store_original_data"] = storeOriginalData.value;
+ settings["enable_distinct_fields"] = enableDistinctFields.value;
settings["approx_partition"] = approxPartition.value;
if (flattenLevel.value !== null) {
@@ -1933,6 +2188,20 @@ export default defineComponent({
: getImageURL("images/common/quick_mode.svg");
});
+ const getConfigIcon = computed(() => {
+ return store.state.theme === "dark"
+ ? getImageURL("images/streams/config_light.svg")
+ : getImageURL("images/streams/config.svg");
+ });
+ const getTimelineIcon = computed(() => {
+ return store.state.theme === "dark"
+ ? getImageURL("images/streams/timeline_light.svg")
+ : getImageURL("images/streams/timeline.svg");
+ });
+
+
+
+
return {
t,
q,
@@ -1949,6 +2218,7 @@ export default defineComponent({
getImageURL,
dataRetentionDays,
storeOriginalData,
+ enableDistinctFields,
approxPartition,
maxQueryRange,
flattenLevel,
@@ -2021,6 +2291,8 @@ export default defineComponent({
updateIndexType,
isEnvQuickModeField,
quickModeIcon,
+ getConfigIcon,
+ getTimelineIcon,
};
},
created() {
@@ -2042,7 +2314,6 @@ export default defineComponent({
padding: 8px 16px;
}
.indexDetailsContainer {
- padding: 1.25rem;
width: 100%;
.title {
@@ -2145,7 +2416,7 @@ export default defineComponent({
.mapping-warning-msg {
background-color: #f9f290;
- padding: 8px 16px;
+ padding: 4px 16px;
border-radius: 4px;
border: 1px solid #f5a623;
color: #865300;
@@ -2186,150 +2457,7 @@ export default defineComponent({
padding: 0;
min-width: 40px;
}
-
-// .sticky-buttons {
-// position: sticky;
-// bottom: 0px;
-// margin: 0 auto;
-// background-color: var(--q-accent);
-// box-shadow: 6px 6px 18px var(--q-accent);
-// justify-content: right;
-// width: 100%;
-// padding-right: 20px;
-// }
-
-// .btn-delete {
-// left: 20px;
-// position: absolute;
-// }
-
-// .sticky-table-header {
-// position: sticky;
-// top: 0px;
-// background: var(--q-accent);
-// z-index: 1;
-// }
-
-// .body--dark {
-// .sticky-table-header {
-// background: var(--q-dark);
-// }
-
-// .sticky-buttons {
-// background-color: var(--q-dark);
-// box-shadow: 6px 6px 18px var(--q-dark);
-// }
-// }
.single-line-tab {
display: inline-flex;
}
-
-
-
+
\ No newline at end of file
diff --git a/web/src/styles/schema.scss b/web/src/styles/schema.scss
new file mode 100644
index 0000000000..fd860f417d
--- /dev/null
+++ b/web/src/styles/schema.scss
@@ -0,0 +1,293 @@
+
+
+
+.add-fields-card {
+ width: 100vw;
+ max-width: 100%;
+ display: flex;
+ flex-direction: column;
+}
+.indexDetailsContainer {
+ .q-table__control {
+ width: 100%;
+ }
+
+ .q-table {
+ td:nth-child(2) {
+ min-width: 15rem;
+ width: 15rem;
+ max-width: 15rem;
+ overflow: auto;
+ scrollbar-width: thin;
+ scrollbar-color: #999 #f0f0f0;
+ }
+ td:nth-child(3) {
+ padding: 4px 8px !important;
+ }
+ }
+
+ th:first-child,
+ td:first-child {
+ padding-left: 8px !important;
+ }
+
+ th:nth-child(5),
+ td:nth-child(5){
+ min-width: 15rem;
+ width: 15rem;
+ max-width: 15rem;
+ }
+}
+.dark-theme-table tr:hover td:nth-child(2) {
+ background-color: #272a2b !important;
+}
+.light-theme-table tr:hover td:nth-child(2) {
+ background-color: #f7f7f7 !important;
+}
+
+.schema-fields-tabs {
+ height: fit-content;
+ .rum-tab {
+ width: fit-content !important;
+ padding: 4px 12px !important;
+ border: none !important;
+
+ &.active {
+ background: #5960b2;
+ color: #ffffff !important;
+ }
+ }
+}
+.main-fields-tabs {
+ height: fit-content;
+ color: var(--q-light);
+ .rum-tab {
+ padding: 4px 12px !important;
+
+ &.active {
+ color: white !important;
+ border-bottom: 5px solid var(--q-primary) !important;
+ background-color: rgba(88, 96, 178, 0.6);
+ }
+ }
+}
+.floating-buttons {
+ position: sticky;
+ bottom: 0;
+ z-index: 1; /* Ensure it stays on top of table content */
+ width: 100%;
+ margin-top: auto; /* Push to bottom when there's less content */
+}
+.dark-theme-floating-buttons {
+ background-color: #181a1b;
+}
+.light-theme-floating-buttons {
+ background-color: #ffffff
+}
+
+.warning-text {
+ color: #f5a623;
+ border: 1px solid #f5a623;
+ border-radius: 10px;
+ padding: 6px 3px;
+}
+.mini-select .q-field__control {
+ min-height: 24px !important;
+ padding: 0px 4px !important;
+}
+
+.mini-select .q-field__control-container {
+ padding-top: 0px !important;
+ padding-bottom: 0px !important;
+ min-height: 20px !important;
+}
+
+.mini-select .q-field__control-container span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ display: inline-block;
+ font-size: 11px !important;
+ line-height: 12px !important;
+}
+
+.mini-select .q-field__marginal {
+ height: 20px !important;
+}
+
+.mini-select.q-field--auto-height.q-field--dense .q-field__control,
+.mini-select.q-field--auto-height.q-field--dense .q-field__native {
+ min-height: 24px !important;
+ height: 24px !important;
+}
+
+.mini-select.q-field--dense .q-field__control,
+.mini-select.q-field--dense .q-field__marginal {
+ min-height: 12px !important;
+ height: 12px !important;
+}
+
+.mini-select .q-field--dense .q-field__control,
+.mini-select .q-field--dense .q-field__marginal {
+ min-height: 12px !important;
+ height: 12px !important;
+}
+
+/* Custom clear icon styling */
+.mini-select .q-icon {
+ font-size: 14px !important;
+ width: 14px !important;
+ height: 14px !important;
+ opacity: 0.7;
+ font-weight: 900 !important;
+ display: flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ line-height: 1 !important;
+ margin-top: 16px !important;
+}
+
+.mini-select .q-icon:hover {
+ opacity: 1;
+}
+
+/* Move dropdown arrow down and add rotation animation */
+.mini-select .q-field__append .q-icon {
+ margin-top: 14px;
+ transition: transform 0.2s ease;
+}
+
+/* Rotate arrow when dropdown is open */
+.mini-select.q-field--focused .q-field__append .q-icon {
+ transform: rotate(180deg);
+}
+.schema-index-select-dropdown-light{
+ background-color: white !important;
+ border-radius: 4px !important;
+ border: 1px solid #C9CCD6 !important;
+}
+
+.schema-index-select-dropdown-light.q-field--focused .q-field__control {
+ outline: 2px solid #575FC5 !important;
+ box-shadow: none !important;
+ background-color: #FFFFFF !important;
+ border-radius: 4px !important;
+}
+
+.schema-index-select-dropdown-dark{
+ background-color: #2B2D30 !important;
+ border-radius: 4px !important;
+ border: 1px solid #4E5157 !important;
+}
+
+.schema-index-select-dropdown-dark.q-field--focused .q-field__control {
+ outline: 2px solid #575FC5 !important;
+ box-shadow: none !important;
+ background-color: #2B2D30 !important;
+ border-radius: 4px !important;
+}
+
+/* Field name ellipsis styling */
+.field-name-ellipsis {
+ max-width: 200px;
+}
+
+.field-name-text {
+ display: inline-block;
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+}
+
+.o2-schema-table{
+ th,td {
+ height: 14px !important;
+ min-height: 14px !important;
+ padding: 2px 3px !important;
+ }
+ thead {
+ position: sticky;
+ top: 0;
+ z-index: 1000;
+ }
+}
+
+.o2-schema-table-header-sticky-dark{
+ thead {
+ background-color: #181A1B;
+ }
+}
+
+.o2-schema-table-header-sticky-light{
+ thead {
+ background-color: #ffffff;
+ }
+}
+
+
+/* Field type badge styling */
+.field-type-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 2px 8px;
+ border-radius: 6px;
+ font-size: 11px;
+ font-weight: 600;
+}
+
+.badge-int64 {
+ background: #dbeafe;
+ color: #1d4ed8;
+}
+
+.badge-float64 {
+ background: #d1fae5;
+ color: #065f46;
+}
+
+.badge-utf8 {
+ background: #fef3c7;
+ color: #92400e;
+}
+
+.schema-input-box{
+ background-color: white !important;
+ border-radius: 4px !important;
+ padding: 0px 8px !important;
+ border: 1px solid #C9CCD6 !important;
+ .q-field__control{
+ color: transparent !important;
+
+ }
+}
+
+.schema-input-box-light{
+ .q-field__control{
+ background-color: #ffffff !important;
+ outline: 0px 1px 0px 1px solid #C9CCD6 !important;
+ padding: 0px 6px !important;
+ }
+}
+
+.schema-input-box-dark{
+ background-color: #2B2D30 !important;
+ border-radius: 4px !important;
+ border: 1px solid #4E5157 !important;
+}
+.bottom-btn {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.tile-content-dark{
+ background-color: #181a1b;
+}
+
+.tile-content-light{
+ background-color: #ffffff;
+}