Gerrrr commented on code in PR #13:
URL: https://github.com/apache/otava-website/pull/13#discussion_r3488765107


##########
src/components/HomepageFeatures/FlowAnimation.tsx:
##########
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useState } from "react";
+import {
+  AnimatePresence,
+  motion,
+  useMotionValueEvent,
+  useReducedMotion,
+  useTime,
+  useTransform,
+} from "motion/react";
+import styles from "./FlowAnimation.module.css";
+
+/**
+ * Banner animation telling the operational story:
+ *   - data is recorded *continuously* from every source into Otava
+ *   - most of the time nothing happens — Otava just records & analyzes
+ *   - only when a real change-point is detected does Otava raise an ALERT
+ *     (to Slack) and surface an action: revert the offending commit
+ */
+
+// SVG geometry
+const W = 880;
+const H = 248;
+
+// New Performance Test Node on the left
+const TEST_X = 24;
+const TEST_W = 140;
+const NODE_H = 34;
+
+// Data Sources shifted to the right
+const SRC_X = 236;
+const SRC_W = 96;
+
+// Otava box shifted to the right
+const BOX_X = 486;
+const BOX_W = 120;
+const BOX_Y = 92;
+const BOX_H = 64;
+const CX = BOX_X + BOX_W / 2;
+const CY = BOX_Y + BOX_H / 2;
+
+// Output nodes on the far right
+const OUT_X = W - 24 - 120; // 880 - 24 - 120 = 736
+const OUT_W = 120;
+
+const SOURCES = ["CSV", "PostgreSQL", "BigQuery", "Graphite"];
+
+const srcY = (i: number) => 40 + i * 56; // 40, 96, 152, 208
+const ALERT_Y = 104;
+const ACTION_Y = 168;
+
+const testFrom = { x: TEST_X + TEST_W, y: CY };
+const testTo = (i: number) => ({ x: SRC_X, y: srcY(i) });
+const inFrom = (i: number) => ({ x: SRC_X + SRC_W, y: srcY(i) });
+const inTo = { x: BOX_X, y: CY };
+const outFrom = { x: BOX_X + BOX_W, y: CY };
+const alertTo = { x: OUT_X, y: ALERT_Y };
+
+const CYCLE = 1.6; // seconds for an in-packet to traverse a lane
+const PACKETS_PER_LANE = 2;
+
+const LOOP_MS = 9000;
+
+// A continuously-streaming data packet (always flowing = constant recording).
+function InPacket({
+  from,
+  to,
+  delay,
+}: {
+  from: { x: number; y: number };
+  to: { x: number; y: number };
+  delay: number;
+}) {
+  return (
+    <motion.circle
+      r={4}
+      className={styles.packetIn}
+      initial={{ cx: from.x, cy: from.y, opacity: 0 }}
+      animate={{
+        cx: [from.x, to.x],
+        cy: [from.y, to.y],
+        opacity: [0, 1, 1, 0],
+      }}
+      transition={{ duration: CYCLE, ease: "linear", repeat: Infinity, delay }}
+    />
+  );
+}
+
+export default function FlowAnimation(): JSX.Element {
+  const reduce = useReducedMotion();
+
+  const time = useTime();
+  const progress = useTransform(time, (t) =>
+    reduce ? 0.75 : (t % LOOP_MS) / LOOP_MS,
+  );
+  const [p, setP] = useState(reduce ? 0.75 : 0);
+  useMotionValueEvent(progress, "change", setP);
+
+  // Most of the loop = quietly recording. A change is "detected" late in the
+  // loop, which fires the alert + action.
+  const alerting = p >= 0.55 && p < 0.92;
+  const status = alerting ? "change detected" : "recording…";
+
+  return (
+    <motion.div
+      className={styles.wrap}
+      initial={{ opacity: 0, y: 16 }}
+      animate={{ opacity: 1, y: 0 }}
+      transition={{ duration: 0.6, ease: "easeOut" }}
+    >
+      <svg
+        viewBox={`0 0 ${W} ${H}`}
+        className={styles.svg}
+        role="img"
+        aria-label="Performance tests run and publish metrics like CPU and 
throughput to CSV, PostgreSQL, BigQuery, or Graphite. Otava continuously 
ingests and monitors these sources, alerting Slack only when a real regression 
is detected."
+      >
+        {/* test-to-source lanes */}
+        {SOURCES.map((_, i) => {
+          const t = testTo(i);
+          return (
+            <line
+              key={`tl${i}`}
+              x1={testFrom.x}
+              y1={testFrom.y}
+              x2={t.x}
+              y2={t.y}
+              className={styles.lane}
+            />
+          );
+        })}
+
+        {/* source-to-otava lanes */}
+        {SOURCES.map((_, i) => {
+          const f = inFrom(i);
+          return (
+            <line
+              key={`il${i}`}
+              x1={f.x}
+              y1={f.y}
+              x2={inTo.x}
+              y2={inTo.y}
+              className={styles.lane}
+            />
+          );
+        })}
+        {/* alert lane (lights up only when alerting) */}
+        <line
+          x1={outFrom.x}
+          y1={outFrom.y}
+          x2={alertTo.x}
+          y2={alertTo.y}
+          className={alerting ? styles.laneAlert : styles.lane}
+        />
+
+        {/* continuous inbound data packets */}
+        {!reduce &&
+          SOURCES.map((_, i) =>
+            Array.from({ length: PACKETS_PER_LANE }).map((__, k) => (
+              <g key={`packets-${i}-${k}`}>
+                {/* 1. from performance tests to data source */}
+                <InPacket
+                  from={testFrom}
+                  to={testTo(i)}
+                  delay={i * 0.16 + (k * CYCLE) / PACKETS_PER_LANE}
+                />
+                {/* 2. from data source to Otava (offset by half a cycle for 
smooth continuity) */}
+                <InPacket
+                  from={inFrom(i)}
+                  to={inTo}
+                  delay={i * 0.16 + (k * CYCLE) / PACKETS_PER_LANE + CYCLE / 2}
+                />
+              </g>
+            )),
+          )}
+
+        {/* alert packet — only emitted while alerting */}
+        <AnimatePresence>
+          {alerting && !reduce && (
+            <motion.circle
+              r={5}
+              className={styles.packetAlert}
+              initial={{ cx: outFrom.x, cy: outFrom.y, opacity: 0 }}
+              animate={{
+                cx: [outFrom.x, alertTo.x],
+                cy: [outFrom.y, alertTo.y],
+                opacity: [0, 1, 1, 1],
+              }}
+              transition={{ duration: 0.9, ease: "easeOut", repeat: Infinity }}
+            />
+          )}
+        </AnimatePresence>
+
+        {/* performance tests node */}
+        <g>
+          <rect
+            x={TEST_X}
+            y={CY - NODE_H / 2}
+            width={TEST_W}
+            height={NODE_H}
+            rx={7}
+            className={styles.testNode}
+          />
+          <text x={TEST_X + TEST_W / 2} y={CY + 4} 
className={styles.nodeLabel}>
+            Performance Tests
+          </text>
+        </g>
+
+        {/* source nodes */}
+        {SOURCES.map((name, i) => (
+          <g key={name}>
+            <rect
+              x={SRC_X}
+              y={srcY(i) - NODE_H / 2}
+              width={SRC_W}
+              height={NODE_H}
+              rx={7}
+              className={styles.srcNode}
+            />
+            <text x={SRC_X + SRC_W / 2} y={srcY(i) + 4} 
className={styles.nodeLabel}>
+              {name}
+            </text>
+          </g>
+        ))}
+
+        {/* Otava engine — flashes on detection */}
+        <motion.rect
+          x={BOX_X}
+          y={BOX_Y}
+          width={BOX_W}
+          height={BOX_H}
+          rx={12}
+          className={alerting ? styles.engineAlert : styles.engine}
+          animate={alerting ? { scale: [1, 1.04, 1] } : { scale: 1 }}
+          transition={{ duration: 0.6, repeat: alerting ? Infinity : 0 }}
+          style={{ transformBox: "fill-box", transformOrigin: "center" }}
+        />
+        <text x={CX} y={CY - 6} className={styles.engineName}>
+          Otava
+        </text>
+        <AnimatePresence mode="wait">
+          <motion.text
+            key={status}
+            x={CX}
+            y={CY + 13}
+            className={alerting ? styles.engineSubAlert : styles.engineSub}
+            initial={{ opacity: 0 }}
+            animate={{ opacity: 1 }}
+            exit={{ opacity: 0 }}
+          >
+            {status}
+          </motion.text>
+        </AnimatePresence>
+
+        {/* alert + action nodes (appear on detection) */}
+        <AnimatePresence>
+          {alerting && (
+            <motion.g
+              initial={{ opacity: 0, x: -8 }}
+              animate={{ opacity: 1, x: 0 }}
+              exit={{ opacity: 0 }}
+              transition={{ type: "spring", stiffness: 240, damping: 22 }}
+            >
+              {/* Slack alert */}
+              <rect
+                x={OUT_X}
+                y={ALERT_Y - NODE_H / 2}
+                width={OUT_W}
+                height={NODE_H}
+                rx={7}
+                className={styles.alertNode}
+              />
+              <text x={OUT_X + OUT_W / 2} y={ALERT_Y + 4} 
className={styles.alertLabel}>
+                ⚠ Slack alert
+              </text>
+
+              {/* recommended action */}
+              <rect
+                x={OUT_X}
+                y={ACTION_Y - NODE_H / 2}
+                width={OUT_W}
+                height={NODE_H}
+                rx={7}
+                className={styles.actionNode}
+              />
+              <text x={OUT_X + OUT_W / 2} y={ACTION_Y + 4} 
className={styles.actionLabel}>
+                ↩ revert PR

Review Comment:
   nit: capital `R` to make it consistent with `Slack ALert`



##########
.gitignore:
##########
@@ -18,3 +18,16 @@
 npm-debug.log*
 yarn-debug.log*
 yarn-error.log*
+pnpm-debug.log*

Review Comment:
   ```
   npm run typecheck
   
   > [email protected] typecheck
   > tsc
   
   src/components/HomepageFeatures/ChangePointDemo.tsx:169:5 - error TS2503: 
Cannot find namespace 'JSX'.
   
   169 }): JSX.Element {
           ~~~
   
   src/components/HomepageFeatures/ChangePointDemo.tsx:495:4 - error TS2503: 
Cannot find namespace 'JSX'.
   
   495 ): JSX.Element[] {
          ~~~
   
   src/components/HomepageFeatures/ChangePointDemo.tsx:530:41 - error TS2503: 
Cannot find namespace 'JSX'.
   
   530 function stableReport(cfg: DemoConfig): JSX.Element[] {
                                               ~~~
   
   src/components/HomepageFeatures/FeaturesGrid.tsx:25:9 - error TS2503: Cannot 
find namespace 'JSX'.
   
   25   icon: JSX.Element;
              ~~~
   
   src/components/HomepageFeatures/FeaturesGrid.tsx:97:41 - error TS2503: 
Cannot find namespace 'JSX'.
   
   97 export default function FeaturesGrid(): JSX.Element {
                                              ~~~
   
   src/components/HomepageFeatures/FlowAnimation.tsx:107:42 - error TS2503: 
Cannot find namespace 'JSX'.
   
   107 export default function FlowAnimation(): JSX.Element {
                                                ~~~
   
   src/components/HomepageFeatures/index.tsx:26:45 - error TS2503: Cannot find 
namespace 'JSX'.
   
   26 export default function HomepageFeatures(): JSX.Element {
                                                  ~~~
   
   src/pages/index.tsx:25:33 - error TS2503: Cannot find namespace 'JSX'.
   
   25 export default function Home(): JSX.Element {
                                      ~~~
   
   
   Found 8 errors in 5 files.
   
   Errors  Files
        3  src/components/HomepageFeatures/ChangePointDemo.tsx:169
        2  src/components/HomepageFeatures/FeaturesGrid.tsx:25
        1  src/components/HomepageFeatures/FlowAnimation.tsx:107
        1  src/components/HomepageFeatures/index.tsx:26
        1  src/pages/index.tsx:25
   ```



##########
src/components/HomepageFeatures/FeaturesGrid.tsx:
##########
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import styles from "./FeaturesGrid.module.css";
+
+interface FeatureItem {
+  title: string;
+  description: string;
+  icon: JSX.Element;
+}
+
+const FEATURES: FeatureItem[] = [
+  {
+    title: "Change Point Detection",
+    description: "Say goodbye to fragile static thresholds. Otava analyzes 
historical distributions to spot persistent performance shifts, ignoring 
temporary spikes and random noise.",
+    icon: (
+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" 
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
+        <path d="M3 3v18h18" />
+        <path d="M18.7 8l-5.1 5.2-2.8-2.7L7 14.3" />
+        <line x1="12" y1="5" x2="12" y2="19" strokeDasharray="3,3" />
+      </svg>
+    ),
+  },
+  {
+    title: "Feature Branch CI",
+    description: "Verify regressions before merging. Run benchmarks against 
your branch and compare the tail performance directly with your main branch 
baseline using the CLI.",
+    icon: (
+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" 
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
+        <line x1="6" y1="3" x2="6" y2="15" />
+        <circle cx="18" cy="6" r="3" />
+        <circle cx="6" cy="18" r="3" />
+        <path d="M18 9a9 9 0 0 1-9 9" />
+      </svg>
+    ),
+  },
+  {
+    title: "Flexible Data Ingestion",
+    description: "Ingest metrics directly from Graphite, PostgreSQL, Google 
BigQuery, or simple CSV files. Integrates smoothly with your existing 
benchmarking infrastructure.",
+    icon: (
+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" 
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
+        <ellipse cx="12" cy="5" rx="9" ry="3" />
+        <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
+        <path d="M3 12c0 1.66 4 3 9 3s9-1.34 9-3" />
+      </svg>
+    ),
+  },
+  {
+    title: "Slack & Grafana Alerts",
+    description: "Automatically annotate Grafana dashboards with detected 
change points and send Slack alerts when regressions are isolated to a specific 
commit range.",
+    icon: (
+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" 
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
+        <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
+        <path d="M13.73 21a2 2 0 0 1-3.46 0" />
+      </svg>
+    ),
+  },
+  {
+    title: "YAML Templates (DRY)",
+    description: "Avoid copy-pasting test definitions. Define metrics, 
thresholds, and data configurations once in templates, then inherit them across 
hundreds of suites.",

Review Comment:
   I am not sure how much this is a feature and whether `hundreds of suites` 
oversells it a tiny bit :)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to