Skip to content

Commit

Permalink
feat: env localstorage
Browse files Browse the repository at this point in the history
  • Loading branch information
congjiujiu authored and forehalo committed Sep 7, 2022
1 parent a0519b5 commit 15fb8f7
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 5 deletions.
9 changes: 8 additions & 1 deletion packages/platform-server/src/db/mysql/property.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
Index,
} from 'typeorm'

import { CookieType, HeaderType } from '@perfsee/shared'
import { CookieType, HeaderType, LocalStorageType } from '@perfsee/shared'

import { ApplicationSetting } from './application-setting.entity'
import type { Project } from './project.entity'
Expand Down Expand Up @@ -178,6 +178,13 @@ export class Environment extends BaseEntity {
@Column({ type: 'boolean', default: false })
disable!: boolean

@Field(() => GraphQLJSON, {
nullable: true,
description: 'extra localStorage value inserted into page',
})
@Column({ type: 'json', nullable: true })
localStorage!: LocalStorageType[]

@OneToMany('SnapshotReport', 'environment')
reports!: SnapshotReport[]

Expand Down
14 changes: 14 additions & 0 deletions packages/platform-server/src/modules/environment/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ export class EnvironmentService {
}
}

// verify localStorage items
// limit key/value length to 1024 bytes
if (patch.localStorage) {
patch.localStorage.forEach(({ key, value }) => {
try {
if (JSON.stringify(key).length > 1024 || JSON.stringify(value).length > 1024) {
throw new UserError(`environment localStorage key or value exceed 1024 bytes.`)
}
} catch (e) {
throw new UserError(`invalid environment localStorage key or value.`)
}
})
}

// create
if (!patch.iid) {
const iid = await this.internalIdService.generate(projectId, InternalIdUsage.Env)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Generated by [AVA](https://avajs.dev).
deviceId: Function functionStub {},
e2eScript: Function functionStub {},
headers: [],
localStorage: [],
reportId: 1,
runs: 1,
throttle: {},
Expand All @@ -24,6 +25,7 @@ Generated by [AVA](https://avajs.dev).
deviceId: 'no',
e2eScript: Function functionStub {},
headers: [],
localStorage: [],
reportId: 2,
runs: 1,
throttle: {
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ test('get lighthouse run data', (t) => {
id: 1,
headers: [],
cookies: [],
localStorage: [],
}),
createMock<Environment>({
id: 2,
headers: [],
cookies: [],
localStorage: [],
}),
]

Expand Down
1 change: 1 addition & 0 deletions packages/platform-server/src/utils/get-lh-run-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export function getLighthouseRunData(
cookies: env.cookies,
e2eScript: page.e2eScript,
runs: page.isE2e || process.env.NODE_ENV === 'development' ? 1 : 5,
localStorage: env.localStorage ?? [],
}
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ import { useModuleState } from '@sigi/react'
import { useCallback, useState, useRef } from 'react'

import { IconWithTips, RequiredTextField } from '@perfsee/components'
import { CookieType, HeaderType } from '@perfsee/shared'
import { CookieType, HeaderType, LocalStorageType } from '@perfsee/shared'

import { EnvSchema, PropertyModule } from '../../../shared'
import { PartialCookie } from '../helper'

import { FormCookies } from './form-cookies'
import { FormHeaders } from './form-headers'
import { FormLocalStorage } from './form-localstorage'

type FromProps = {
defaultEnv?: EnvSchema
Expand All @@ -55,6 +56,7 @@ export const EnvEditForm = (props: FromProps) => {
const nameRef = useRef<ITextField>({ value: '' } as any)
const headersRef = useRef<{ getHeaders: () => HeaderType[] }>()
const cookiesRef = useRef<{ getCookies: () => PartialCookie[] }>()
const localStorageRef = useRef<{ getLocalStorage: () => LocalStorageType[] }>()
const [isCompetitor, setAsCompetitor] = useState<boolean>(!!defaultEnv?.isCompetitor)
const [zone, setZone] = useState(defaultEnv?.zone ?? defaultZone)

Expand All @@ -63,6 +65,7 @@ export const EnvEditForm = (props: FromProps) => {
const name = nameRef.current.value
const cookies = cookiesRef.current?.getCookies() ?? []
const headers = headersRef.current?.getHeaders() ?? []
const localStorage = localStorageRef.current?.getLocalStorage() ?? []

// Not allowed to save
const conditions = [
Expand All @@ -81,6 +84,7 @@ export const EnvEditForm = (props: FromProps) => {
name,
headers,
cookies,
localStorage,
isCompetitor,
needReminder,
zone,
Expand All @@ -106,6 +110,7 @@ export const EnvEditForm = (props: FromProps) => {
<RequiredTextField id="name" label="Environment name" defaultValue={defaultEnv?.name} componentRef={nameRef} />
<FormHeaders defaultHeaders={defaultEnv?.headers ?? []} ref={headersRef} />
<FormCookies defaultCookies={(defaultEnv?.cookies ?? []) as CookieType[]} ref={cookiesRef} />
<FormLocalStorage defaultLocalStorage={defaultEnv?.localStorage ?? []} ref={localStorageRef} />
<ComboBox label="Zone" selectedKey={zone} options={zones} onChange={onZoneChange} useComboBoxAsMenuWidth />
<Stack tokens={{ childrenGap: 8 }} horizontal horizontalAlign="space-between" verticalAlign="end">
<Stack.Item shrink={false}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
Copyright 2022 ByteDance and/or its affiliates.
Licensed 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 { DefaultButton, Label, Stack, IconButton, SharedColors, TextField } from '@fluentui/react'
import { FormEvent, forwardRef, useCallback, useImperativeHandle, useMemo, useState, memo } from 'react'

import { LocalStorageType } from '@perfsee/shared'

import { NormalToken } from '../style'

type FormStorageProps = {
index: number
item: LocalStorageType
onStorageRemove: (index: number) => void
onStorageChange: (item: LocalStorageType, index: number) => void
}

const FormStorage = memo((props: FormStorageProps) => {
const { index, item, onStorageRemove, onStorageChange } = props

const onRemove = useCallback(() => {
onStorageRemove(index)
}, [index, onStorageRemove])

const onChange = useCallback(
(e?: FormEvent<HTMLElement | HTMLInputElement>, value?: string) => {
if (!e || value === undefined) {
return
}
const type = (e.target as HTMLInputElement).dataset.type!
onStorageChange({ ...item, [type]: value }, index)
},
[index, item, onStorageChange],
)

const inputValidate = useCallback((value: string) => {
if (!value) {
return 'Required'
}

if (value.length > 1024) {
return 'length exceed 1024 bytes'
}
}, [])

return (
<div>
<Stack horizontal horizontalAlign="space-between" verticalAlign="center" tokens={{ padding: '8px 0 0 0' }}>
LocalStorage #{index + 1}
<IconButton
iconProps={{ iconName: 'delete' }}
styles={{ root: { color: SharedColors.red10 }, rootHovered: { color: SharedColors.red10 } }}
onClick={onRemove}
/>
</Stack>
<Stack horizontal tokens={NormalToken}>
<TextField
value={item.key}
data-type="key"
onChange={onChange}
styles={{ root: { flexGrow: 2 } }}
placeholder="Key"
onGetErrorMessage={inputValidate}
/>
<TextField
data-type="value"
onChange={onChange}
value={item.value}
styles={{ root: { flexGrow: 2 } }}
placeholder="Value"
onGetErrorMessage={inputValidate}
/>
</Stack>
</div>
)
})

type Props = { defaultLocalStorage: LocalStorageType[] }

export const FormLocalStorage = forwardRef((props: Props, ref) => {
const [storages, setStorages] = useState<LocalStorageType[]>(props.defaultLocalStorage)

useImperativeHandle(
ref,
() => ({
getLocalStorage: () => storages,
}),
[storages],
)

const onStorageChange = useCallback(
(storage: LocalStorageType, index: number) => {
const newStorages = [...storages]
newStorages[index] = storage
setStorages(newStorages)
},
[storages],
)

const onStorageRemove = useCallback((index: number) => {
setStorages((storages) => {
storages.splice(index, 1)
return [...storages]
})
}, [])

const onAddStorage = useCallback(() => {
setStorages([...storages, { key: '', value: '' }])
}, [storages])

const newHeaders = useMemo(() => {
return storages.map((item, i) => {
return (
<FormStorage
key={i}
index={i}
item={item}
onStorageChange={onStorageChange}
onStorageRemove={onStorageRemove}
/>
)
})
}, [onStorageChange, onStorageRemove, storages])

return (
<>
<Stack horizontal horizontalAlign="space-between" tokens={{ padding: '8px 0 0 0' }}>
<Label htmlFor="localStorage">LocalStorage</Label>
<DefaultButton onClick={onAddStorage}>add localStorage</DefaultButton>
</Stack>
{newHeaders}
</>
)
})
25 changes: 22 additions & 3 deletions packages/runner/lab/src/lighthouse/lighthouse-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ import { v4 as uuid } from 'uuid'

import { JobWorker } from '@perfsee/job-runner-shared'
import { LabJobPayload } from '@perfsee/server-common'
import { CookieType, LighthouseScoreMetric, MetricKeyType, MetricType, RequestSchema } from '@perfsee/shared'
import {
CookieType,
LighthouseScoreMetric,
LocalStorageType,
MetricKeyType,
MetricType,
RequestSchema,
} from '@perfsee/shared'
import { computeMainThreadTasksWithTimings } from '@perfsee/tracehouse'

import {
Expand All @@ -40,6 +47,7 @@ import { computeMedianRun, getFCP, getNumericValue, getTTI, lighthouse, MetricsR
export abstract class LighthouseJobWorker extends JobWorker<LabJobPayload> {
protected headers!: HostHeaders
protected cookies!: CookieType[]
protected localStorageContent!: LocalStorageType[]

protected async before() {
this.warmupPageLoad()
Expand Down Expand Up @@ -168,17 +176,18 @@ export abstract class LighthouseJobWorker extends JobWorker<LabJobPayload> {
private warmupPageLoad() {
this.logger.info('Start warming up page load environment.')

const { headers, cookies } = this.payload
const { headers, cookies, localStorage } = this.payload
const hostHeaders = transformHeadersToHostHeaders(headers)

this.headers = hostHeaders
this.cookies = cookies
this.localStorageContent = localStorage

this.logger.verbose('Warming up ended.')
}

private async runLighthouse() {
const { cookies, headers } = this
const { cookies, headers, localStorageContent } = this
const { url, deviceId, throttle, runs } = this.payload
const device = DEVICE_DESCRIPTORS[deviceId] ?? DEVICE_DESCRIPTORS['no']

Expand All @@ -187,6 +196,7 @@ export abstract class LighthouseJobWorker extends JobWorker<LabJobPayload> {
cookies,
headers,
throttle,
localStorageContent,
})

const downloadKbps = throttle.download ? throttle.download / 125 : 40000
Expand Down Expand Up @@ -232,6 +242,15 @@ export abstract class LighthouseJobWorker extends JobWorker<LabJobPayload> {
}
await page.setCookie(...cookies)
await page.setViewport(device.viewport)

if (localStorageContent.length) {
await page.evaluateOnNewDocument((localStorageContent: LocalStorageType[]) => {
localStorage.clear()
localStorageContent.forEach(({ key, value }) => {
localStorage.setItem(key, value)
})
}, localStorageContent)
}
}

setup().catch((e) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ fragment environmentFields on Environment {
name
cookies
headers
localStorage
isCompetitor
disable
needReminder
Expand Down
6 changes: 6 additions & 0 deletions packages/schema/src/server-schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,9 @@ type Environment {

"""Disable scanning of this environment"""
disable: Boolean!

"""extra localStorage value inserted into page"""
localStorage: JSON
}

"""message target setting"""
Expand Down Expand Up @@ -1274,6 +1277,9 @@ input UpdateEnvironmentInput {

"""Disable scanning of this environment"""
disable: Boolean

"""extra localStorage value inserted into page"""
localStorage: JSON
}

input UpdateProfileInput {
Expand Down
2 changes: 2 additions & 0 deletions packages/server-common/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
HeaderType,
CookieType,
MetricKeyType,
LocalStorageType,
} from '@perfsee/shared'

type PartialSnapshotReport = {
Expand All @@ -44,6 +45,7 @@ export interface LabJobPayload {
runs: number
headers: HeaderType[]
cookies: CookieType[]
localStorage: LocalStorageType[]
}

export type E2EJobPayload = LabJobPayload & {
Expand Down

0 comments on commit 15fb8f7

Please sign in to comment.