Compare commits

..

1 Commits

Author SHA1 Message Date
Nav Singh
71e5276c8e fix(telemetry): set first_auth_at person property via $set_once on auth
Closes MAR-172.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 21:14:57 -07:00
5 changed files with 32 additions and 80 deletions

View File

@@ -128,8 +128,7 @@ export const TestIds = {
pinIndicator: 'node-pin-indicator',
innerWrapper: 'node-inner-wrapper',
mainImage: 'main-image',
slotConnectionDot: 'slot-connection-dot',
imageGrid: 'image-grid'
slotConnectionDot: 'slot-connection-dot'
},
selectionToolbox: {
root: 'selection-toolbox',

View File

@@ -15,9 +15,7 @@ export class VueNodeFixture {
public readonly root: Locator
public readonly widgets: Locator
public readonly imagePreview: Locator
public readonly imageGrid: Locator
public readonly content: Locator
public readonly resize: { bottomRight: Locator }
constructor(private readonly locator: Locator) {
this.header = locator.locator('[data-testid^="node-header-"]')
@@ -30,10 +28,7 @@ export class VueNodeFixture {
this.root = locator
this.widgets = this.locator.locator('.lg-node-widget')
this.imagePreview = locator.locator('.image-preview')
this.imageGrid = locator.getByTestId(TestIds.node.imageGrid)
this.content = locator.locator('.lg-node-content')
const bottomRight = locator.getByRole('button', { name: 'bottom-right' })
this.resize = { bottomRight }
}
async getTitle(): Promise<string> {

View File

@@ -1,15 +1,11 @@
import { expect, mergeTests } from '@playwright/test'
import type { Locator } from '@playwright/test'
import { expect } from '@playwright/test'
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
import { ExecutionHelper } from '@e2e/fixtures/helpers/ExecutionHelper'
import {
getPromotedWidgetNames,
getPromotedWidgetCountByName
} from '@e2e/fixtures/utils/promotedWidgets'
import { webSocketFixture } from '@e2e/fixtures/ws'
const wstest = mergeTests(test, webSocketFixture)
test.describe('Vue Nodes Image Preview', { tag: '@vue-nodes' }, () => {
async function loadImageOnNode(comfyPage: ComfyPage) {
@@ -142,44 +138,3 @@ test.describe('Vue Nodes Image Preview', { tag: '@vue-nodes' }, () => {
}
)
})
async function countColumns(locator: Locator) {
return await locator.locator('img').evaluateAll((images) => {
const yOffsets = images.map((image) => image.getBoundingClientRect().y)
return yOffsets.filter((yOffset) => yOffset === yOffsets[0]).length
})
}
test.describe('Vue Nodes Batch Image Preview', { tag: '@vue-nodes' }, () => {
wstest(
'Image previews tile to fit node',
async ({ comfyMouse, comfyPage, getWebSocket }) => {
const execution = new ExecutionHelper(comfyPage, await getWebSocket())
await test.step('Add node', async () => {
await comfyPage.menu.topbar.newWorkflowButton.click()
await comfyPage.nextFrame()
await comfyPage.searchBoxV2.addNode('Preview Image')
const previewImage = comfyPage.vueNodes.getNodeByTitle('Preview Image')
await expect(previewImage).toBeVisible()
})
const node = await comfyPage.vueNodes.getFixtureByTitle('Preview Image')
await test.step('Inject multiple previews', async () => {
const file = { filename: 'example.png', type: 'input' }
const images = new Array(100).fill(file)
execution.executed('', '1', { images })
await expect(node.imageGrid.locator('img')).toHaveCount(100)
})
const { bottomRight } = node.resize
await expect.poll(() => countColumns(node.imageGrid)).toBe(10)
await comfyMouse.resizeByDragging(bottomRight, { x: 200 })
await expect.poll(() => countColumns(node.imageGrid)).toBeGreaterThan(10)
await comfyMouse.resizeByDragging(bottomRight, { x: -200, y: 200 })
await expect.poll(() => countColumns(node.imageGrid)).toBeLessThan(10)
}
)
})

View File

@@ -233,7 +233,10 @@ export class PostHogTelemetryProvider implements TelemetryProvider {
}
trackAuth(metadata: AuthMetadata): void {
this.trackEvent(TelemetryEvents.USER_AUTH_COMPLETED, metadata)
this.captureRaw(TelemetryEvents.USER_AUTH_COMPLETED, {
...metadata,
$set_once: { first_auth_at: new Date().toISOString() }
})
}
trackUserLoggedIn(): void {

View File

@@ -7,32 +7,30 @@
<!-- Grid View -->
<div
v-if="viewMode === 'grid'"
ref="gridEl"
data-testid="image-grid"
class="relative grid w-full flex-1 gap-1 rounded-sm p-1 contain-size"
class="group/panel relative grid w-full gap-1 overflow-hidden rounded-sm p-1"
:style="{ gridTemplateColumns: `repeat(${gridCols}, 1fr)` }"
>
<Button
<button
v-for="(url, index) in imageUrls"
:key="index"
size="unset"
class="ring-ring overflow-hidden rounded-none p-0 hover:ring-1 focus-visible:ring-2"
class="focus-visible:ring-ring relative cursor-pointer overflow-hidden rounded-sm border-0 bg-transparent p-0 focus-visible:ring-2 focus-visible:outline-none"
:aria-label="
$t('g.viewImageOfTotal', {
index: index + 1,
total: imageUrls.length
})
"
@click="openImageInGallery(index)"
@pointerdown="trackPointerStart"
@click="handleGridThumbnailClick($event, index)"
>
<img
:src="url"
:alt="`${$t('g.galleryThumbnail')} ${index + 1}`"
draggable="false"
class="pointer-events-none size-full object-contain"
@load="updateAspectRatio($event, index)"
/>
</Button>
</button>
</div>
<!-- Gallery View (Image Wrapper) -->
@@ -169,12 +167,11 @@
</template>
<script setup lang="ts">
import { useElementSize, useTimeoutFn } from '@vueuse/core'
import { computed, nextTick, ref, useTemplateRef, watch } from 'vue'
import { useTimeoutFn } from '@vueuse/core'
import { computed, nextTick, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { downloadFile } from '@/base/common/downloadUtil'
import Button from '@/components/ui/button/Button.vue'
import Skeleton from '@/components/ui/skeleton/Skeleton.vue'
import { useMaskEditor } from '@/composables/maskeditor/useMaskEditor'
import { useToastStore } from '@/platform/updates/common/toastStore'
@@ -205,17 +202,12 @@ function defaultViewMode(urls: readonly string[]): ViewMode {
return urls.length > 1 ? 'grid' : 'gallery'
}
const { width: gridWidth, height: gridHeight } = useElementSize(
useTemplateRef('gridEl')
)
const currentIndex = ref(0)
const viewMode = ref<ViewMode>(defaultViewMode(imageUrls))
const galleryPanelEl = ref<HTMLDivElement>()
const actualDimensions = ref<string | null>(null)
const imageError = ref(false)
const showLoader = ref(false)
const imageAspectRatio = ref(1)
const { start: startDelayedLoader, stop: stopDelayedLoader } = useTimeoutFn(
() => {
@@ -235,8 +227,10 @@ const imageAltText = computed(() =>
})
)
const gridCols = computed(() => {
const bias = gridWidth.value / gridHeight.value / imageAspectRatio.value
return Math.max(Math.round(Math.sqrt(imageUrls.length * bias)), 1)
const count = imageUrls.length
if (count <= 4) return 2
if (count <= 9) return 3
return 4
})
watch(
@@ -280,14 +274,6 @@ function handleImageLoad(event: Event) {
}
}
function updateAspectRatio(event: Event, index: number) {
if (!(event.target instanceof HTMLImageElement) || index !== 0) return
const { naturalWidth, naturalHeight } = event.target
if (naturalWidth && naturalHeight) {
imageAspectRatio.value = naturalWidth / naturalHeight
}
}
function handleImageError() {
stopDelayedLoader()
showLoader.value = false
@@ -324,6 +310,20 @@ function setCurrentIndex(index: number) {
}
}
const CLICK_THRESHOLD = 3
let pointerStartPos = { x: 0, y: 0 }
function trackPointerStart(event: PointerEvent) {
pointerStartPos = { x: event.clientX, y: event.clientY }
}
function handleGridThumbnailClick(event: MouseEvent, index: number) {
const dx = event.clientX - pointerStartPos.x
const dy = event.clientY - pointerStartPos.y
if (Math.abs(dx) > CLICK_THRESHOLD || Math.abs(dy) > CLICK_THRESHOLD) return
openImageInGallery(index)
}
async function openImageInGallery(index: number) {
setCurrentIndex(index)
viewMode.value = 'gallery'