diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 9bdb7e3c..47a4e076 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -7,7 +7,7 @@ module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
- 'plugin:@typescript-eslint/recommended',
+ 'plugin:@typescript-eslint/recommended-type-checked',
'plugin:import/recommended',
'plugin:import/electron',
'plugin:import/typescript',
@@ -18,7 +18,11 @@ module.exports = {
'prettier',
],
parser: '@typescript-eslint/parser',
- ignorePatterns: ['resources/group_snippet.js', 'install-k6.js'],
+ ignorePatterns: [
+ 'resources/group_snippet.js',
+ 'install-k6.js',
+ '.eslintrc.cjs',
+ ],
plugins: [
'import',
'unused-imports',
@@ -43,11 +47,16 @@ module.exports = {
argsIgnorePattern: '^_',
},
],
- // TODO: remove warnings when plugin:@typescript-eslint/recommended-type-checked is enabled
- '@typescript-eslint/no-unsafe-assignment': 'warn',
- '@typescript-eslint/no-unsafe-return': 'warn',
- '@typescript-eslint/no-unsafe-argument': 'warn',
- '@typescript-eslint/no-unsafe-member-access': 'warn',
+ '@typescript-eslint/no-floating-promises': [
+ 'error',
+ {
+ ignoreIIFE: true,
+ },
+ ],
+ '@typescript-eslint/no-misused-promises': [
+ 'error',
+ { checksVoidReturn: false },
+ ],
},
parserOptions: {
diff --git a/package-lock.json b/package-lock.json
index 17ebc3fa..6f9d354e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11345,9 +11345,9 @@
"integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now=="
},
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
diff --git a/src/ErrorElement.tsx b/src/ErrorElement.tsx
index 2434ef1a..cf05abae 100644
--- a/src/ErrorElement.tsx
+++ b/src/ErrorElement.tsx
@@ -10,11 +10,10 @@ import { css } from '@emotion/react'
import GrotCrashed from '@/assets/grot-crashed.svg'
import { ArrowLeftIcon } from '@radix-ui/react-icons'
-const handleCreateIssue = () => {
+const handleCreateIssue = () =>
window.studio.browser.openExternalLink(
'https://github.com/grafana/k6-studio/issues'
)
-}
const handleOpenLogs = () => {
window.studio.log.openLogFolder()
diff --git a/src/browser.ts b/src/browser.ts
index 9ca5bd6c..83a7744d 100644
--- a/src/browser.ts
+++ b/src/browser.ts
@@ -70,7 +70,7 @@ export const launchBrowser = async (
`--proxy-server=http://localhost:${appSettings.proxy.port}`,
`--ignore-certificate-errors-spki-list=${certificateSPKI}`,
disableChromeOptimizations,
- url ?? '',
+ url?.trim() || 'about:blank',
],
onExit: sendBrowserClosedEvent,
})
diff --git a/src/codegen/codegen.utils.ts b/src/codegen/codegen.utils.ts
index c8089031..5a260801 100644
--- a/src/codegen/codegen.utils.ts
+++ b/src/codegen/codegen.utils.ts
@@ -24,6 +24,8 @@ export function stringify(value: unknown): string {
return `{${properties}}`
}
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
return `${value}`
}
diff --git a/src/components/ExperimentalBanner.tsx b/src/components/ExperimentalBanner.tsx
index 294fc01c..2a6145a0 100644
--- a/src/components/ExperimentalBanner.tsx
+++ b/src/components/ExperimentalBanner.tsx
@@ -1,11 +1,10 @@
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
import { Flex, Text, Link } from '@radix-ui/themes'
-const handleLinkClick = () => {
+const handleLinkClick = () =>
window.studio.browser.openExternalLink(
'https://github.com/grafana/k6-studio/issues'
)
-}
export function ExperimentalBanner() {
return (
diff --git a/src/components/Form/FileUploadInput.tsx b/src/components/Form/FileUploadInput.tsx
index ac7e1c77..d9cab6df 100644
--- a/src/components/Form/FileUploadInput.tsx
+++ b/src/components/Form/FileUploadInput.tsx
@@ -39,6 +39,8 @@ export const FileUploadInput = ({
disabled={disabled}
onChange={field.onChange}
name={field.name}
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
value={field.value}
/>
diff --git a/src/components/Layout/ActivityBar/HelpButton.tsx b/src/components/Layout/ActivityBar/HelpButton.tsx
index b0caa5e1..fe38730a 100644
--- a/src/components/Layout/ActivityBar/HelpButton.tsx
+++ b/src/components/Layout/ActivityBar/HelpButton.tsx
@@ -2,17 +2,15 @@ import { QuestionMarkCircledIcon } from '@radix-ui/react-icons'
import { DropdownMenu, IconButton, Tooltip } from '@radix-ui/themes'
export function HelpButton() {
- const handleOpenDocs = () => {
+ const handleOpenDocs = () =>
window.studio.browser.openExternalLink(
'https://grafana.com/docs/k6-studio/'
)
- }
- const handleReportIssue = () => {
+ const handleReportIssue = () =>
window.studio.browser.openExternalLink(
'https://github.com/grafana/k6-studio/issues'
)
- }
const handleOpenApplicationLogs = () => {
window.studio.log.openLogFolder()
diff --git a/src/components/Layout/ActivityBar/ProxyStatusIndicator.tsx b/src/components/Layout/ActivityBar/ProxyStatusIndicator.tsx
index b1b6a483..7521a709 100644
--- a/src/components/Layout/ActivityBar/ProxyStatusIndicator.tsx
+++ b/src/components/Layout/ActivityBar/ProxyStatusIndicator.tsx
@@ -18,11 +18,10 @@ export function ProxyStatusIndicator() {
const setProxyStatus = useStudioUIStore((state) => state.setProxyStatus)
useEffect(() => {
- async function fetchProxyStatus() {
+ ;(async function fetchProxyStatus() {
const status = await window.studio.proxy.getProxyStatus()
setProxyStatus(status)
- }
- fetchProxyStatus()
+ })()
return window.studio.proxy.onProxyStatusChange((status) =>
setProxyStatus(status)
diff --git a/src/components/Layout/Sidebar/Sidebar.hooks.ts b/src/components/Layout/Sidebar/Sidebar.hooks.ts
index dc71dbc5..fc0ab5d8 100644
--- a/src/components/Layout/Sidebar/Sidebar.hooks.ts
+++ b/src/components/Layout/Sidebar/Sidebar.hooks.ts
@@ -26,13 +26,14 @@ function useFolderContent() {
const setFolderContent = useStudioUIStore((s) => s.setFolderContent)
useEffect(() => {
- window.studio.ui.getFiles().then((files) => {
+ ;(async () => {
+ const files = await window.studio.ui.getFiles()
setFolderContent({
recordings: toFileMap(files.recordings),
generators: toFileMap(files.generators),
scripts: toFileMap(files.scripts),
})
- })
+ })()
}, [setFolderContent])
useEffect(
diff --git a/src/components/Layout/Sidebar/Sidebar.tsx b/src/components/Layout/Sidebar/Sidebar.tsx
index 915c2a79..2babb372 100644
--- a/src/components/Layout/Sidebar/Sidebar.tsx
+++ b/src/components/Layout/Sidebar/Sidebar.tsx
@@ -28,78 +28,81 @@ export function Sidebar({ isExpanded, onCollapseSidebar }: SidebarProps) {
maxWidth="100%"
overflow="hidden"
position="relative"
+ asChild
>
-
-
-
- {isExpanded && (
-
+
+
-
-
- )}
-
-
-
-
-
+ onChange={setSearchTerm}
+ />
+
+ {isExpanded && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ }
+ />
+
-
-
-
+
- >
- }
- />
-
-
-
-
-
- }
- />
-
-
-
+ }
+ />
+
+
+
+
)
}
diff --git a/src/components/Settings/LogsSettings.tsx b/src/components/Settings/LogsSettings.tsx
index 34b1c637..d5ea034a 100644
--- a/src/components/Settings/LogsSettings.tsx
+++ b/src/components/Settings/LogsSettings.tsx
@@ -17,13 +17,11 @@ export function LogsSettings() {
// retrieve the current content of the log file
useEffect(() => {
- async function fetchLogContent() {
+ ;(async function fetchLogContent() {
const content = await window.studio.log.getLogContent()
setLogContent(content)
scrollToLastLine()
- }
-
- fetchLogContent()
+ })()
}, [scrollToLastLine])
// subscribe to log changes
@@ -45,14 +43,14 @@ export function LogsSettings() {
return (
-
+
-
- k6 Studio logs in this screen are updated in real-time.
+
+ Application logs are updated in real-time.
diff --git a/src/components/Settings/Settings.hooks.ts b/src/components/Settings/Settings.hooks.ts
new file mode 100644
index 00000000..56523987
--- /dev/null
+++ b/src/components/Settings/Settings.hooks.ts
@@ -0,0 +1,26 @@
+import { queryClient } from '@/utils/query'
+import { useMutation, useQuery } from '@tanstack/react-query'
+
+export function useSettings() {
+ return useQuery({
+ queryKey: ['settings'],
+ queryFn: window.studio.settings.getSettings,
+ })
+}
+
+export function useSaveSettings(onSuccess?: () => void) {
+ return useMutation({
+ mutationFn: window.studio.settings.saveSettings,
+ onSuccess: async (isSuccessful) => {
+ if (!isSuccessful) {
+ return
+ }
+
+ await queryClient.invalidateQueries({ queryKey: ['settings'] })
+ onSuccess?.()
+ },
+ onError: (error) => {
+ console.error('Error saving settings', error)
+ },
+ })
+}
diff --git a/src/components/Settings/SettingsDialog.tsx b/src/components/Settings/SettingsDialog.tsx
index bea6ff6f..76189eca 100644
--- a/src/components/Settings/SettingsDialog.tsx
+++ b/src/components/Settings/SettingsDialog.tsx
@@ -3,7 +3,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { Box, Button, Dialog, Flex, ScrollArea, Tabs } from '@radix-ui/themes'
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'
import { findIndex, sortBy } from 'lodash-es'
-import { useEffect, useState } from 'react'
+import { useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { ProxySettings } from './ProxySettings'
@@ -14,6 +14,7 @@ import { UsageReportSettings } from './UsageReportSettings'
import { ButtonWithTooltip } from '../ButtonWithTooltip'
import { AppearanceSettings } from './AppearanceSettings'
import { LogsSettings } from './LogsSettings'
+import { useSaveSettings, useSettings } from './Settings.hooks'
type SettingsDialogProps = {
open: boolean
@@ -41,18 +42,12 @@ const tabs = [
]
export const SettingsDialog = ({ open, onOpenChange }: SettingsDialogProps) => {
- const [settings, setSettings] = useState()
- const [submitting, setSubmitting] = useState(false)
+ const { data: settings } = useSettings()
+ const { mutateAsync: saveSettings, isPending } = useSaveSettings(() => {
+ onOpenChange(false)
+ })
const [selectedTab, setSelectedTab] = useState('proxy')
- useEffect(() => {
- async function fetchSettings() {
- const data = await window.studio.settings.getSettings()
- setSettings(data)
- }
- fetchSettings()
- }, [])
-
const formMethods = useForm({
resolver: zodResolver(AppSettingsSchema),
shouldFocusError: true,
@@ -65,23 +60,6 @@ export const SettingsDialog = ({ open, onOpenChange }: SettingsDialogProps) => {
formState: { isDirty, errors },
} = formMethods
- const onSubmit = async (data: AppSettings) => {
- try {
- setSubmitting(true)
- const isSuccess = await window.studio.settings.saveSettings(data)
- if (isSuccess) {
- onOpenChange(false)
- reset(data)
- setSettings(data)
- setSelectedTab('proxy')
- }
- } catch (error) {
- console.error('Error saving settings', error)
- } finally {
- setSubmitting(false)
- }
- }
-
const onInvalid = (errors: Record) => {
// Sort tabs by the order they appear in the UI
const tabsWithError = sortBy(Object.keys(errors), (key) =>
@@ -159,10 +137,13 @@ export const SettingsDialog = ({ open, onOpenChange }: SettingsDialogProps) => {
saveSettings(data),
+ onInvalid
+ )}
>
Save changes
diff --git a/src/components/Settings/UsageReportSettings.tsx b/src/components/Settings/UsageReportSettings.tsx
index 170e827b..3ff2a9aa 100644
--- a/src/components/Settings/UsageReportSettings.tsx
+++ b/src/components/Settings/UsageReportSettings.tsx
@@ -6,11 +6,10 @@ import { AppSettings } from '@/types/settings'
export const UsageReportSettings = () => {
const { control, register } = useFormContext()
- const handleLinkClick = () => {
+ const handleLinkClick = () =>
window.studio.browser.openExternalLink(
'https://grafana.com/docs/k6-studio/set-up/usage-collection/'
)
- }
return (
diff --git a/src/components/WebLogView/Group.tsx b/src/components/WebLogView/Group.tsx
index 24b42edc..46220f9a 100644
--- a/src/components/WebLogView/Group.tsx
+++ b/src/components/WebLogView/Group.tsx
@@ -95,7 +95,7 @@ export function Group({
useClickAway(headerRef, () => {
if (!group.isEditing) return
- handleSubmit(submit)()
+ return handleSubmit(submit)()
})
const isValidName = (value: string) => {
diff --git a/src/components/WebLogView/RequestDetails/utils.ts b/src/components/WebLogView/RequestDetails/utils.ts
index e5361551..aaf3eb3b 100644
--- a/src/components/WebLogView/RequestDetails/utils.ts
+++ b/src/components/WebLogView/RequestDetails/utils.ts
@@ -28,6 +28,8 @@ export function parseParams(data: ProxyData) {
}
return stringify(
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
JSON.parse(jsonrepair(parsePythonByteString(contentDecoded)))
)
} catch (e) {
diff --git a/src/components/WebLogView/ResponseDetails/Font.tsx b/src/components/WebLogView/ResponseDetails/Font.tsx
index 17f387c3..0310f57b 100644
--- a/src/components/WebLogView/ResponseDetails/Font.tsx
+++ b/src/components/WebLogView/ResponseDetails/Font.tsx
@@ -6,19 +6,17 @@ export function Font({ url }: { url: string }) {
const [isReady, setIsReady] = useState(false)
const [name, setName] = useState('')
- async function addFontFace(url: string) {
- const name = uniqueId('font-')
- const font = new FontFace(name, `url(${url})`)
-
- await font.load()
- document.fonts.add(font)
- setIsReady(true)
- setName(name)
- }
-
useEffect(() => {
- setIsReady(false)
- addFontFace(url)
+ ;(async function addFontFace(url: string) {
+ setIsReady(false)
+ const name = uniqueId('font-')
+ const font = new FontFace(name, `url(${url})`)
+
+ await font.load()
+ document.fonts.add(font)
+ setIsReady(true)
+ setName(name)
+ })(url)
}, [url])
if (!isReady) {
diff --git a/src/components/WebLogView/ResponseDetails/Preview.tsx b/src/components/WebLogView/ResponseDetails/Preview.tsx
index 98742e14..d66c2765 100644
--- a/src/components/WebLogView/ResponseDetails/Preview.tsx
+++ b/src/components/WebLogView/ResponseDetails/Preview.tsx
@@ -53,6 +53,8 @@ export function Preview({ content, contentType, format }: PreviewProps) {
return (
field.name !== 'root'}
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
src={JSON.parse(content)}
theme={theme === 'dark' ? 'monokai' : 'rjv-default'}
style={theme === 'dark' ? reactJsonDarkStyles : reactJsonLightStyles}
diff --git a/src/components/WebLogView/ResponseDetails/ResponseDetails.utils.ts b/src/components/WebLogView/ResponseDetails/ResponseDetails.utils.ts
index 2b119650..03b8581c 100644
--- a/src/components/WebLogView/ResponseDetails/ResponseDetails.utils.ts
+++ b/src/components/WebLogView/ResponseDetails/ResponseDetails.utils.ts
@@ -49,8 +49,12 @@ export function parseContent(format: string | undefined, data: ProxyData) {
try {
switch (format) {
case 'json':
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return stringify(JSON.parse(safeAtob(content)))
case 'json-raw':
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return stringify(JSON.parse(safeAtob(content)), 0)
case 'css':
case 'html':
diff --git a/src/components/WebLogView/Row.tsx b/src/components/WebLogView/Row.tsx
index eba46f48..d308aa7f 100644
--- a/src/components/WebLogView/Row.tsx
+++ b/src/components/WebLogView/Row.tsx
@@ -6,6 +6,7 @@ import { MethodBadge } from '../MethodBadge'
import { ResponseStatusBadge } from '../ResponseStatusBadge'
import { TableCellWithTooltip } from '../TableCellWithTooltip'
import { HighlightedText } from '../HighlightedText'
+import { getRequestType } from './WebLogView.utils'
interface RowProps {
data: ProxyDataWithMatches
@@ -53,6 +54,7 @@ export function Row({ data, isSelected, onSelectRequest }: RowProps) {
/>
+ {getRequestType(data)}
diff --git a/src/components/WebLogView/WebLogView.tsx b/src/components/WebLogView/WebLogView.tsx
index 7c66e475..3ba5b15b 100644
--- a/src/components/WebLogView/WebLogView.tsx
+++ b/src/components/WebLogView/WebLogView.tsx
@@ -93,6 +93,7 @@ function RequestList({
Method
Status
+ Type
Host
Path
diff --git a/src/components/WebLogView/WebLogView.utils.ts b/src/components/WebLogView/WebLogView.utils.ts
index 50ac307a..1a0d4220 100644
--- a/src/components/WebLogView/WebLogView.utils.ts
+++ b/src/components/WebLogView/WebLogView.utils.ts
@@ -1,4 +1,6 @@
import { GroupedProxyData, ProxyData } from '@/types'
+import { getContentType } from '@/utils/headers'
+import { isNonStaticAssetResponse } from '@/utils/staticAssets'
export function removeQueryStringFromUrl(url: string) {
return url.split('?')[0]
@@ -20,3 +22,12 @@ export function isGroupedProxyData(
): data is GroupedProxyData {
return !Array.isArray(data)
}
+
+export function getRequestType(data: ProxyData) {
+ if (isNonStaticAssetResponse(data)) {
+ return 'fetch'
+ }
+
+ const mimeType = getContentType(data.response?.headers ?? [])
+ return mimeType ? mimeType.split('/')[1] : 'unknown'
+}
diff --git a/src/hooks/useRunChecks.test.ts b/src/hooks/useRunChecks.test.ts
index b9a6ffab..d3d87b05 100644
--- a/src/hooks/useRunChecks.test.ts
+++ b/src/hooks/useRunChecks.test.ts
@@ -41,7 +41,7 @@ describe('useRunChecks', () => {
createK6Check({ id: '1', name: 'Check 1' }),
createK6Check({ id: '2', name: 'Check 2' }),
]
- onScriptCheck.mockImplementation((callback) => {
+ onScriptCheck.mockImplementation((callback: (data: K6Check[]) => void) => {
callback(mockChecks)
return () => {}
})
diff --git a/src/hooks/useRunLogs.test.ts b/src/hooks/useRunLogs.test.ts
index 17946996..7f85d76a 100644
--- a/src/hooks/useRunLogs.test.ts
+++ b/src/hooks/useRunLogs.test.ts
@@ -3,6 +3,7 @@ import { useRunLogs } from './useRunLogs'
import { renderHook } from '@testing-library/react'
import { act } from 'react'
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
+import { K6Log } from '@/types'
const onScriptLog = vi.fn()
@@ -37,7 +38,7 @@ describe('useRunLogs', () => {
it('should update logs when onScriptLog is called', () => {
const mockLog = createK6Log()
- onScriptLog.mockImplementation((callback) => {
+ onScriptLog.mockImplementation((callback: (log: K6Log) => void) => {
callback(mockLog)
return () => {}
})
diff --git a/src/hooks/useScriptPreview.test.ts b/src/hooks/useScriptPreview.test.ts
index b22506f7..9755ee66 100644
--- a/src/hooks/useScriptPreview.test.ts
+++ b/src/hooks/useScriptPreview.test.ts
@@ -16,7 +16,7 @@ import {
} from '@/test/factories/generator'
vi.mock('lodash-es', () => ({
- debounce: vi.fn((fn) => fn),
+ debounce: vi.fn((fn: () => void) => fn),
}))
vi.mock('@/store/generator', () => ({
useGeneratorStore: {
diff --git a/src/hooks/useScriptPreview.ts b/src/hooks/useScriptPreview.ts
index c68eaf7d..7b20e2b9 100644
--- a/src/hooks/useScriptPreview.ts
+++ b/src/hooks/useScriptPreview.ts
@@ -30,6 +30,8 @@ export function useScriptPreview() {
}, 100)
// Initial preview generation
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
updatePreview(useGeneratorStore.getState())
const unsubscribe = useGeneratorStore.subscribe((state) =>
diff --git a/src/main.ts b/src/main.ts
index 9c4ad33a..6beec69f 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -113,7 +113,7 @@ const createSplashWindow = async () => {
splashscreenWindow.webContents.openDevTools({ mode: 'detach' })
}
- splashscreenWindow.loadFile(splashscreenFile)
+ await splashscreenWindow.loadFile(splashscreenFile)
// wait for the window to be ready before showing it. It prevents showing a white page on longer load times.
splashscreenWindow.once('ready-to-show', () => {
@@ -158,9 +158,9 @@ const createWindow = async () => {
// and load the index.html of the app.
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
- mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL)
+ await mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL)
} else {
- mainWindow.loadFile(
+ await mainWindow.loadFile(
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
)
}
@@ -195,16 +195,21 @@ const createWindow = async () => {
return mainWindow
}
-app.whenReady().then(async () => {
- await initSettings()
- appSettings = await getSettings()
- nativeTheme.themeSource = appSettings.appearance.theme
+app.whenReady().then(
+ async () => {
+ await initSettings()
+ appSettings = await getSettings()
+ nativeTheme.themeSource = appSettings.appearance.theme
- await sendReport(appSettings.usageReport)
- await createSplashWindow()
- await setupProjectStructure()
- await createWindow()
-})
+ await sendReport(appSettings.usageReport)
+ await createSplashWindow()
+ await setupProjectStructure()
+ await createWindow()
+ },
+ (error) => {
+ log.error(error)
+ }
+)
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
@@ -228,14 +233,14 @@ app.on('before-quit', async () => {
// stop watching files to avoid crash on exit
appShuttingDown = true
await watcher.close()
- stopProxyProcess()
+ return stopProxyProcess()
})
-ipcMain.handle('app:change-route', async (_, route: string) => {
+ipcMain.on('app:change-route', (_, route: string) => {
currentClientRoute = route
})
-ipcMain.handle('app:close', (event) => {
+ipcMain.on('app:close', (event) => {
console.log('app:close event received')
wasAppClosedByClient = true
@@ -255,9 +260,9 @@ ipcMain.handle('proxy:start', async (event) => {
currentProxyProcess = await launchProxyAndAttachEmitter(browserWindow)
})
-ipcMain.on('proxy:stop', async () => {
+ipcMain.on('proxy:stop', () => {
console.info('proxy:stop event received')
- stopProxyProcess()
+ return stopProxyProcess()
})
const waitForProxy = async (): Promise => {
@@ -286,7 +291,7 @@ ipcMain.handle('browser:start', async (event, url?: string) => {
ipcMain.on('browser:stop', async () => {
console.info('browser:stop event received')
if (currentBrowserProcess) {
- currentBrowserProcess.close()
+ await currentBrowserProcess.close()
currentBrowserProcess = null
}
})
@@ -415,8 +420,12 @@ ipcMain.handle('har:open', async (_, fileName: string): Promise => {
try {
fileHandle = await open(path.join(RECORDINGS_PATH, fileName), 'r')
const data = await fileHandle?.readFile({ encoding: 'utf-8' })
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const har = await JSON.parse(data)
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
return { name: fileName, content: har }
} finally {
await fileHandle?.close()
@@ -470,8 +479,12 @@ ipcMain.handle(
fileHandle = await open(path.join(GENERATORS_PATH, fileName), 'r')
const data = await fileHandle?.readFile({ encoding: 'utf-8' })
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const generator = await JSON.parse(data)
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
return { name: fileName, content: generator }
} finally {
await fileHandle?.close()
@@ -490,7 +503,7 @@ ipcMain.handle('ui:delete-file', async (_, fileName: string) => {
return unlink(getFilePathFromName(fileName))
})
-ipcMain.on('ui:open-folder', async (_, fileName: string) => {
+ipcMain.on('ui:open-folder', (_, fileName: string) => {
console.info('ui:open-folder event received')
shell.showItemInFolder(getFilePathFromName(fileName))
})
@@ -559,10 +572,10 @@ ipcMain.on('splashscreen:close', (event) => {
ipcMain.handle('browser:open:external:link', (_, url: string) => {
console.info('browser:open:external:link event received')
- shell.openExternal(url)
+ return shell.openExternal(url)
})
-ipcMain.handle('log:open', () => {
+ipcMain.on('log:open', () => {
console.info('log:open event received')
openLogFolder()
})
@@ -585,7 +598,7 @@ ipcMain.handle('settings:save', async (event, data: AppSettings) => {
// don't pass fields that are not submitted by the form
const { windowState: _, ...settings } = data
const modifiedSettings = await saveSettings(settings)
- applySettings(modifiedSettings, browserWindow)
+ await applySettings(modifiedSettings, browserWindow)
sendToast(browserWindow.webContents, {
title: 'Settings saved successfully',
@@ -610,7 +623,7 @@ ipcMain.handle('settings:select-upstream-certificate', async () => {
return selectUpstreamCertificate()
})
-ipcMain.handle('proxy:status:get', async () => {
+ipcMain.handle('proxy:status:get', () => {
console.info('proxy:status:get event received')
return proxyStatus
})
@@ -706,7 +719,7 @@ function showWindow(browserWindow: BrowserWindow) {
browserWindow.focus()
}
-function trackWindowState(browserWindow: BrowserWindow) {
+async function trackWindowState(browserWindow: BrowserWindow) {
const { width, height, x, y } = browserWindow.getBounds()
const isMaximized = browserWindow.isMaximized()
appSettings.windowState = {
@@ -717,7 +730,7 @@ function trackWindowState(browserWindow: BrowserWindow) {
isMaximized,
}
try {
- saveSettings(appSettings)
+ await saveSettings(appSettings)
} catch (error) {
log.error(error)
}
diff --git a/src/preload.ts b/src/preload.ts
index c2485c6c..be844c84 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -1,3 +1,5 @@
+// TODO: https://github.com/grafana/k6-studio/issues/277
+/* eslint-disable @typescript-eslint/no-unsafe-return */
import { ipcRenderer, contextBridge, IpcRendererEvent } from 'electron'
import { ProxyData, K6Log, K6Check, ProxyStatus } from './types'
import { HarFile } from './types/har'
@@ -34,7 +36,7 @@ const proxy = {
onProxyData: (callback: (data: ProxyData) => void) => {
return createListener('proxy:data', callback)
},
- getProxyStatus: () => {
+ getProxyStatus: (): Promise => {
return ipcRenderer.invoke('proxy:status:get')
},
onProxyStatusChange: (callback: (status: ProxyStatus) => void) => {
@@ -159,16 +161,16 @@ const app = {
return createListener('app:close', callback)
},
closeApplication: () => {
- ipcRenderer.invoke('app:close')
+ ipcRenderer.send('app:close')
},
changeRoute: (route: string) => {
- return ipcRenderer.invoke('app:change-route', route)
+ return ipcRenderer.send('app:change-route', route)
},
} as const
const log = {
openLogFolder: () => {
- ipcRenderer.invoke('log:open')
+ ipcRenderer.send('log:open')
},
getLogContent: (): Promise => {
return ipcRenderer.invoke('log:read')
diff --git a/src/proxy.ts b/src/proxy.ts
index 92fe33f8..40ddc99a 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -94,7 +94,7 @@ export const launchProxy = (
})
proxy.stderr.on('data', (data: Buffer) => {
- console.error(`stderr: ${data}`)
+ console.error(`stderr: ${data.toString()}`)
log.error(data.toString())
})
diff --git a/src/rules/correlation.ts b/src/rules/correlation.ts
index e4e116a7..2e946418 100644
--- a/src/rules/correlation.ts
+++ b/src/rules/correlation.ts
@@ -61,7 +61,7 @@ function applyRule({
requestSnippetSchema: RequestSnippetSchema
state: CorrelationState
rule: CorrelationRule
- idGenerator: Generator
+ idGenerator: IdGenerator
setState: (newState: Partial) => void
}) {
// this is the modified schema that we return to the accumulator
@@ -108,6 +108,8 @@ function applyRule({
}
// try to extract the value
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { extractedValue, generatedUniqueId, correlationExtractionSnippet } =
tryCorrelationExtraction(
rule,
@@ -126,7 +128,9 @@ function applyRule({
}
setState({
- extractedValue: extractedValue,
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ extractedValue,
generatedUniqueId: generatedUniqueId,
responsesExtracted: [
...state.responsesExtracted,
@@ -542,9 +546,14 @@ const extractCorrelationJsonBody = (
return noCorrelationResult
}
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const extractedValue = getJsonObjectFromPath(response.content, selector.path)
- if (!extractedValue || extractedValue.length === 0) {
+ if (
+ !extractedValue ||
+ (Array.isArray(extractedValue) && extractedValue.length === 0)
+ ) {
return noCorrelationResult
}
@@ -558,6 +567,8 @@ const extractCorrelationJsonBody = (
const correlationExtractionSnippet = `
correlation_vars['correlation_${generatedUniqueId}'] = resp.json()${json_path}`
return {
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
extractedValue,
correlationExtractionSnippet,
generatedUniqueId,
diff --git a/src/rules/parameterization.ts b/src/rules/parameterization.ts
index acdad2e7..fb6f8d6d 100644
--- a/src/rules/parameterization.ts
+++ b/src/rules/parameterization.ts
@@ -10,7 +10,7 @@ import { matchFilter } from './utils'
export function createParameterizationRuleInstance(
rule: ParameterizationRule,
- idGenerator: Generator
+ idGenerator: Generator
): ParameterizationRuleInstance {
const state: ParameterizationState = {
requestsReplaced: [],
diff --git a/src/rules/rules.ts b/src/rules/rules.ts
index aeebf0ea..b8b8ddda 100644
--- a/src/rules/rules.ts
+++ b/src/rules/rules.ts
@@ -8,7 +8,10 @@ import { ProxyData, RequestSnippetSchema } from '@/types'
import { generateSequentialInt } from './utils'
function createSequentialIdPool() {
- const currentId: Record> = {
+ const currentId: Record<
+ TestRule['type'],
+ Generator
+ > = {
correlation: generateSequentialInt(),
parameterization: generateSequentialInt(),
verification: generateSequentialInt(),
@@ -37,7 +40,7 @@ export function applyRules(recording: ProxyData[], rules: TestRule[]) {
function createRuleInstance(
rule: T,
- idGenerator: Generator
+ idGenerator: Generator
) {
switch (rule.type) {
case 'correlation':
diff --git a/src/rules/shared.ts b/src/rules/shared.ts
index 326a1bd0..c196cd81 100644
--- a/src/rules/shared.ts
+++ b/src/rules/shared.ts
@@ -116,6 +116,8 @@ export const matchRegex = (value: string, regexString: string) => {
}
export const getJsonObjectFromPath = (json: string, path: string) => {
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
return get(safeJsonParse(json), path)
}
@@ -272,6 +274,8 @@ export const replaceJsonBody = (
// since we are using lodash and its `set` function creates missing paths we will first check that the path really
// exists before setting it
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const valueToReplace = getJsonObjectFromPath(request.content, selector.path)
if (!valueToReplace) return
diff --git a/src/script.ts b/src/script.ts
index 213a4f40..c0b240c9 100644
--- a/src/script.ts
+++ b/src/script.ts
@@ -83,11 +83,15 @@ export const runScript = async (
stdoutReader.on('line', (data) => {
console.log(`stdout: ${data}`)
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const checkData: K6Check[] = JSON.parse(data)
browserWindow.webContents.send('script:check', checkData)
})
stderrReader.on('line', (data) => {
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const logData: K6Log = JSON.parse(data)
browserWindow.webContents.send('script:log', logData)
})
diff --git a/src/settings.ts b/src/settings.ts
index d2983f72..1c1722df 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -55,7 +55,11 @@ export async function getSettings() {
const fileHandle = await open(filePath, 'r')
try {
const settings = await fileHandle?.readFile({ encoding: 'utf-8' })
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const currentSettings = JSON.parse(settings)
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const allSettings = {
...defaultSettings,
...currentSettings,
diff --git a/src/utils/format.ts b/src/utils/format.ts
index 51d742b9..eb9e340c 100644
--- a/src/utils/format.ts
+++ b/src/utils/format.ts
@@ -1,7 +1,11 @@
export function queryStringToJSON(str: string) {
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
return JSON.parse(
'{"' + str.replace(/&/g, '","').replace(/=/g, '":"') + '"}',
function (key, value) {
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument
return key === '' ? value : decodeURIComponent(value)
}
)
diff --git a/src/utils/harToProxyData.ts b/src/utils/harToProxyData.ts
index 884f8123..87297477 100644
--- a/src/utils/harToProxyData.ts
+++ b/src/utils/harToProxyData.ts
@@ -41,7 +41,9 @@ function parseRequest(request: Entry['request']): Request {
// TODO: add actual values
// @ts-expect-error incomplete type
timestampStart: request.startedDateTime
- ? // @ts-expect-error incomplete type
+ ? // TODO: https://github.com/grafana/k6-studio/issues/277
+ // @ts-expect-error incomplete type
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
isoToUnixTimestamp(request.startedDateTime)
: 0,
timestampEnd: 0,
diff --git a/src/views/Generator/ExportScriptDialog/OverwriteFileWarning.tsx b/src/views/Generator/ExportScriptDialog/OverwriteFileWarning.tsx
index 1dce607f..b7ee89ce 100644
--- a/src/views/Generator/ExportScriptDialog/OverwriteFileWarning.tsx
+++ b/src/views/Generator/ExportScriptDialog/OverwriteFileWarning.tsx
@@ -9,6 +9,8 @@ export function OverwriteFileWarning() {
setValue('overwriteFile', false)
}
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
const scriptName = getScriptNameWithExtension(getValues('scriptName'))
return (
diff --git a/src/views/Generator/Generator.hooks.ts b/src/views/Generator/Generator.hooks.ts
index af6d1e1f..448f5038 100644
--- a/src/views/Generator/Generator.hooks.ts
+++ b/src/views/Generator/Generator.hooks.ts
@@ -52,7 +52,7 @@ export function useSaveGeneratorFile(fileName: string) {
return useMutation({
mutationFn: async (generator: GeneratorFileData) => {
await writeGeneratorToFile(fileName, generator)
- queryClient.invalidateQueries({ queryKey: ['generator', fileName] })
+ await queryClient.invalidateQueries({ queryKey: ['generator', fileName] })
},
onSuccess: () => {
diff --git a/src/views/Generator/Generator.tsx b/src/views/Generator/Generator.tsx
index e7108b5a..85dfd4ab 100644
--- a/src/views/Generator/Generator.tsx
+++ b/src/views/Generator/Generator.tsx
@@ -58,6 +58,8 @@ export function Generator() {
const blocker = useBlocker(({ historyAction }) => {
// Don't block navigation when redirecting home from invalid generator
+ // TODO(router): Action enum is not exported from react-router-dom
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
return isDirty && historyAction !== 'REPLACE'
})
diff --git a/src/views/Generator/Generator.utils.ts b/src/views/Generator/Generator.utils.ts
index 7a92cae1..412a5ad8 100644
--- a/src/views/Generator/Generator.utils.ts
+++ b/src/views/Generator/Generator.utils.ts
@@ -22,17 +22,13 @@ export async function generateScriptPreview(
return prettify(script)
}
-export function saveScript(script: string, fileName: string) {
- window.studio.script.saveScript(script, fileName)
-}
-
export async function exportScript(fileName: string) {
const generator = selectGeneratorData(useGeneratorStore.getState())
const filteredRequests = selectFilteredRequests(useGeneratorStore.getState())
const script = await generateScriptPreview(generator, filteredRequests)
- saveScript(script, fileName)
+ await window.studio.script.saveScript(script, fileName)
}
export const scriptExists = async (fileName: string) => {
diff --git a/src/views/Generator/RuleEditor/CustomCodeEditor.tsx b/src/views/Generator/RuleEditor/CustomCodeEditor.tsx
index 91c5c48f..5e545138 100644
--- a/src/views/Generator/RuleEditor/CustomCodeEditor.tsx
+++ b/src/views/Generator/RuleEditor/CustomCodeEditor.tsx
@@ -34,6 +34,8 @@ export function CustomCodeEditor() {
render={({ field }) => (
{
field.onChange(value)
diff --git a/src/views/Generator/ValidatorDialog.tsx b/src/views/Generator/ValidatorDialog.tsx
index 7ab0c1bf..3aedd1b4 100644
--- a/src/views/Generator/ValidatorDialog.tsx
+++ b/src/views/Generator/ValidatorDialog.tsx
@@ -58,6 +58,8 @@ export function ValidatorDialog({
useEffect(() => {
if (!open) return
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
handleRunScript()
}, [open, handleRunScript])
diff --git a/src/views/Recorder/Recorder.tsx b/src/views/Recorder/Recorder.tsx
index 6a6b93c1..85a2a043 100644
--- a/src/views/Recorder/Recorder.tsx
+++ b/src/views/Recorder/Recorder.tsx
@@ -117,7 +117,7 @@ export function Recorder() {
}
}, [groups, proxyData, startUrl])
- async function handleStopRecording() {
+ function handleStopRecording() {
stopRecording()
}
diff --git a/src/views/RecordingPreviewer/RecordingPreviewer.tsx b/src/views/RecordingPreviewer/RecordingPreviewer.tsx
index 1993d048..a37d71e0 100644
--- a/src/views/RecordingPreviewer/RecordingPreviewer.tsx
+++ b/src/views/RecordingPreviewer/RecordingPreviewer.tsx
@@ -25,7 +25,11 @@ export function RecordingPreviewer() {
const [selectedRequest, setSelectedRequest] = useState(null)
const { fileName } = useParams()
const navigate = useNavigate()
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { state } = useLocation()
+ // TODO: https://github.com/grafana/k6-studio/issues/277
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const isDiscardable = Boolean(state?.discardable)
invariant(fileName, 'fileName is required')
diff --git a/src/views/Validator/Validator.hooks.ts b/src/views/Validator/Validator.hooks.ts
index fe9c861b..0c2f7e58 100644
--- a/src/views/Validator/Validator.hooks.ts
+++ b/src/views/Validator/Validator.hooks.ts
@@ -2,7 +2,8 @@ import { useLocation, useParams } from 'react-router-dom'
export function useScriptPath() {
const { fileName } = useParams()
- const { state } = useLocation()
+ // TODO(router): useLocation is not type-safe. Refactor this route to avoid using it.
+ const { state } = useLocation() as { state: { externalScriptPath: string } }
return {
scriptPath: fileName || state?.externalScriptPath,
diff --git a/src/views/Validator/Validator.tsx b/src/views/Validator/Validator.tsx
index 85e614f1..2085ae4c 100644
--- a/src/views/Validator/Validator.tsx
+++ b/src/views/Validator/Validator.tsx
@@ -60,7 +60,7 @@ export function Validator() {
navigate(getRoutePath('home'))
}
- function handleRunScript() {
+ async function handleRunScript() {
if (!scriptPath) {
return
}
@@ -68,8 +68,8 @@ export function Validator() {
resetProxyData()
resetLogs()
resetChecks()
- window.studio.script.runScript(scriptPath, isExternal)
setIsRunning(true)
+ await window.studio.script.runScript(scriptPath, isExternal)
}
function handleStopScript() {