This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new de3df85  ui: logs legend at top of table (drop service facet) + 
workflow notes
de3df85 is described below

commit de3df851ac5dd20afc055cdce04b4ef2fad61f63
Author: Wu Sheng <[email protected]>
AuthorDate: Thu May 14 21:03:33 2026 +0800

    ui: logs legend at top of table (drop service facet) + workflow notes
    
    Logs view:
    - Drop the 'Service (top 8)' facet rail — the query is already
      service-scoped (route is /layer/<key>/logs with a selected
      service), so a top-services list just repeats the title.
    - Move the Level legend to the top of the log stream as a chip row
      (one chip per level + count when data exists). Filter still works
      by click. Sample size moved to the right edge.
    - Drop the now-dead .lg-facets / .facet-* CSS + @media variant.
---
 apps/ui/src/views/layer/LayerLogsView.vue | 132 +++++++++++++-----------------
 1 file changed, 58 insertions(+), 74 deletions(-)

diff --git a/apps/ui/src/views/layer/LayerLogsView.vue 
b/apps/ui/src/views/layer/LayerLogsView.vue
index 4bad659..cde535e 100644
--- a/apps/ui/src/views/layer/LayerLogsView.vue
+++ b/apps/ui/src/views/layer/LayerLogsView.vue
@@ -206,17 +206,9 @@ const levelFacet = computed<Record<Level, number>>(() => {
   for (const r of logs.value) counts[levelOf(r)] += 1;
   return counts;
 });
-const serviceFacet = computed<Array<[string, number]>>(() => {
-  if (facets.value?.services && facets.value.services.length > 0) {
-    return facets.value.services.slice(0, 8).map((s) => [s.name, s.count]);
-  }
-  const map = new Map<string, number>();
-  for (const r of logs.value) {
-    const k = r.serviceName ?? '(none)';
-    map.set(k, (map.get(k) ?? 0) + 1);
-  }
-  return [...map.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);
-});
+// Service facet removed — the log query is already service-scoped
+// (the view is opened from /layer/<key>/logs with a specific service
+// selected), so a "top services" rail just repeats the title.
 
 // Since the level filter now goes to OAP, the visible logs already
 // reflect it — no client-side narrowing needed.
@@ -301,40 +293,32 @@ function jumpToTrace(traceId: string): void {
 
     <!-- Histogram + main stream -->
     <section class="lg-body sw-card">
-      <aside class="lg-facets">
-        <div class="facet-block">
-          <div class="facet-title">
-            Level
-            <span v-if="facets" class="facet-sample" :title="`window sample of 
${facets.sampled} rows`">·{{ facets.sampled }}</span>
-          </div>
+      <div class="lg-main">
+        <!-- Top-of-table legend strip — one chip per level with the
+             in-window count when data exists. Clickable: toggles the
+             level filter. The service axis is intentionally absent
+             (this query is already service-scoped, so the service
+             dimension carries no information). -->
+        <div v-if="facets || logs.length > 0" class="lg-legend">
+          <span class="lg-legend-kicker">Levels</span>
           <button
             v-for="l in LEVEL_ORDER"
             :key="l"
             type="button"
-            class="facet-row"
+            class="lg-legend-chip"
             :class="{ on: selectedLevel === l, disabled: l === 'other' }"
             :disabled="l === 'other'"
             @click="toggleLevel(l)"
           >
             <span class="lvl-dot" :style="{ background: LEVEL_COLOR[l] }" />
-            <span class="facet-name">{{ l }}</span>
-            <span class="facet-count">{{ levelFacet[l] }}</span>
+            <span class="lg-legend-name">{{ l }}</span>
+            <span v-if="levelFacet[l] > 0" class="lg-legend-count">{{ 
levelFacet[l] }}</span>
           </button>
+          <span v-if="facets" class="lg-legend-sample" :title="`window sample 
of ${facets.sampled} rows`">
+            sample of {{ facets.sampled }}
+          </span>
         </div>
-        <div class="facet-block">
-          <div class="facet-title">Service (top 8)</div>
-          <div
-            v-for="[name, count] in serviceFacet"
-            :key="name"
-            class="facet-row svc-row"
-          >
-            <span class="facet-name mono">{{ name }}</span>
-            <span class="facet-count">{{ count }}</span>
-          </div>
-        </div>
-      </aside>
 
-      <div class="lg-main">
         <!-- Density bar -->
         <div class="lg-density" v-if="histogram.bins.length > 0">
           <div
@@ -484,55 +468,62 @@ function jumpToTrace(traceId: string): void {
 
 .lg-body {
   padding: 0;
-  display: grid;
-  grid-template-columns: 200px 1fr;
   min-height: 540px;
 }
-.lg-facets {
-  border-right: 1px solid var(--sw-line);
-  padding: 12px;
+/* Top-of-table level legend — chips sit above the density bar so the
+   level counts surface at the same scan line the user reads the
+   timeline. Clicking a chip filters the stream to that level. */
+.lg-legend {
   display: flex;
-  flex-direction: column;
-  gap: 14px;
+  align-items: center;
+  gap: 6px;
+  padding: 10px 14px;
+  border-bottom: 1px solid var(--sw-line);
+  flex-wrap: wrap;
 }
-.facet-block {}
-.facet-title {
-  font-size: 9.5px;
+.lg-legend-kicker {
+  font-size: 10.5px;
   text-transform: uppercase;
   letter-spacing: 0.06em;
   color: var(--sw-fg-3);
-  margin-bottom: 6px;
+  margin-right: 6px;
 }
-.facet-row {
-  display: flex;
+.lg-legend-chip {
+  display: inline-flex;
   align-items: center;
-  gap: 8px;
-  width: 100%;
-  padding: 3px 6px;
-  background: transparent;
-  border: none;
-  border-radius: 3px;
+  gap: 6px;
+  padding: 3px 9px;
+  background: var(--sw-bg-2);
+  border: 1px solid var(--sw-line-2);
+  border-radius: 12px;
   color: var(--sw-fg-1);
   font: inherit;
-  font-size: 11px;
+  font-size: 11.5px;
   cursor: pointer;
-  text-align: left;
 }
-.facet-row.svc-row { cursor: default; }
-.facet-row:hover { background: var(--sw-bg-2); }
-.facet-row.on {
-  background: var(--sw-accent-soft);
+.lg-legend-chip:hover { color: var(--sw-fg-0); border-color: var(--sw-line); }
+.lg-legend-chip.on {
   color: var(--sw-accent-2);
+  background: var(--sw-accent-soft);
+  border-color: var(--sw-accent-line);
 }
-.facet-row.disabled { cursor: not-allowed; opacity: 0.55; }
-.facet-row.disabled:hover { background: transparent; }
-.facet-sample {
-  margin-left: 4px;
-  font-size: 9.5px;
+.lg-legend-chip.disabled { opacity: 0.45; cursor: not-allowed; }
+.lg-legend-name { text-transform: capitalize; }
+.lg-legend-count {
+  font-family: var(--sw-mono);
+  font-size: 11px;
+  font-weight: 600;
+  color: var(--sw-fg-2);
+  padding: 0 4px;
+  border-left: 1px solid var(--sw-line-2);
+  margin-left: 2px;
+}
+.lg-legend-chip.on .lg-legend-count { color: var(--sw-accent-2); border-color: 
var(--sw-accent-line); }
+.lg-legend-sample {
+  margin-left: auto;
+  font-size: 10.5px;
   color: var(--sw-fg-3);
   font-family: var(--sw-mono);
-  text-transform: none;
-  letter-spacing: 0;
 }
 .lvl-dot {
   width: 8px;
@@ -540,13 +531,6 @@ function jumpToTrace(traceId: string): void {
   border-radius: 2px;
   flex: 0 0 auto;
 }
-.facet-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: 
ellipsis; white-space: nowrap; }
-.facet-name.mono { font-family: var(--sw-mono); font-size: 10.5px; }
-.facet-count {
-  font-family: var(--sw-mono);
-  color: var(--sw-fg-3);
-  font-size: 10.5px;
-}
 
 .lg-main {
   display: flex;
@@ -696,7 +680,7 @@ function jumpToTrace(traceId: string): void {
 }
 
 @media (max-width: 1100px) {
-  .lg-body { grid-template-columns: 1fr; }
-  .lg-facets { border-right: none; border-bottom: 1px solid var(--sw-line); 
flex-direction: row; flex-wrap: wrap; }
+  .lg-legend { padding: 8px 10px; gap: 4px; }
+  .lg-legend-chip { padding: 2px 7px; font-size: 11px; }
 }
 </style>

Reply via email to