Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Simpler default file names #453

Merged
merged 5 commits into from
Feb 10, 2025

Conversation

going-confetti
Copy link
Collaborator

@going-confetti going-confetti commented Feb 5, 2025

Description

  • Make auto-generated file names shorter and reduce noise, by removing the timestamp and using optional increment at the end.
  • Remove GeneratorFile as it's become redundant
  • Stringify HAR/Generator data in the main process
image

How to Test

Verify that the following works as before (apart from the new filenames):

  • Create an new generator, then do it again on the same date
  • Create a few new recordings with the same starting URL
  • Create a new recording without a starting URL
  • Try re-configuring a generator and saving changes

Copy link
Collaborator Author

@going-confetti going-confetti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-review

@@ -13,11 +11,7 @@ export function useCreateGenerator() {

const createTestGenerator = useCallback(async () => {
try {
const newGenerator = createNewGeneratorFile()
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to do it in the render process

src/main.ts Outdated Show resolved Hide resolved
src/main.ts Outdated
@@ -962,3 +985,35 @@ const cleanUpProxies = async () => {
kill(proc.pid)
})
}

const generateUniqueFileName = async ({
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in a util file, but I'd need to create a new one, since utils/file.ts has some exports that are used in render :/

Copy link
Collaborator

@allansson allansson Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where having the app split up based on it's entry point would be handy:

shared/
  - utils/
    - file.ts
main/
  - index.ts
render/
  - utils/
    - file.ts
  - index.ts

This would allows us to setup a no-restricted-imports rule in ESLint so that things in render/ can't import from main/ and vice versa.

@@ -5,7 +5,7 @@ export function createEmptyRule(type: TestRule['type']): TestRule {
case 'correlation':
return {
type: 'correlation',
id: self.crypto.randomUUID(),
id: crypto.randomUUID(),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: although it's not a big deal, should we stop storing rule ids in the generator file?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, I believe it's only used by the state manager (so we know each one is selected/being edited etc). It might be easier to have an id on file than setting than during runtime 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should store them because:

  1. They are convenient when writing the frontend, e.g. for key props.
  2. If you diff two files, you can tell whether it was one thing being changed or one thing being removed and another added.
  3. JavaScript in general has no concept of value equality when it comes to complex types, so being able to easily check for equality via an id is very handy.

Like @cristianoventura said, setting them during runtime is harder.

You'd have to traverse the entire hierarchy of objects and slap and id on the things you care about, and if you load the thing from disk again you'll get an entirely new object with new id:s.

For instance, consider checking if a file has unsaved changes. If the id:s aren't stored, we would have to strip all the ids from everything before doing any comparison.

That said, could we switch to nanoid? No need to use crypto-grade randomness and nanoids are a bit terser than UUIDs. 😅

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, could we switch to nanoid? No need to use crypto-grade randomness and nanoids are a bit terser than UUIDs. 😅

We could, but what would be the benefits of this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but what would be the benefits of this?

None, really. A bit more efficient (crypto is computationally expensive), saves a few bytes of space and IMO they are easier to read than UUIDs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the last argument is the strongest one, but these ids aren't user-facing so I wouldn't make it a priority.
As for performance, in the benchmarks I found crypto.randomUUID() had much higher number of operations per second than nanoid(), but it probably doesn't make any measurable difference in the context of k6 studio

const generatorFile = await window.studio.generator.loadGenerator(fileName)
return GeneratorFileDataSchema.parse(generatorFile.content)
const generator = await window.studio.generator.loadGenerator(fileName)
return GeneratorFileDataSchema.parse(generator)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this should be happening in main

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll open a separate PR for this change

JSON.stringify(har, null, 4),
prefix
)
const prefix = getHostNameFromURL(startUrl) ?? 'Recording'
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's another change: if no starting URL is provided, the name will follow the Recording - YYYY-MM-DD pattern

@going-confetti going-confetti marked this pull request as ready for review February 5, 2025 10:50
@going-confetti going-confetti requested a review from a team as a code owner February 5, 2025 10:50
Copy link
Collaborator

@cristianoventura cristianoventura left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works as expected! 🚀

I left a comment for clarification before approving.

src/main.ts Outdated Show resolved Hide resolved
src/main.ts Outdated
Comment on lines 1002 to 1004
let fileExists = await access(path.join(directory, uniqueFileName))
.then(() => true)
.catch(() => false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why we shouldn't use fs.existsSync here instead? It'd look semantically more appropriate as it returns a boolean value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using async fs functions follows the electron performance recommendations and fs.access is the recommended replacement of the deprecated async fs.exists.

Interestingly, there's the following statement about fs.exists:

Using fs.exists() to check for the existence of a file before calling fs.open(), fs.readFile(), or fs.writeFile() is not recommended. Doing so introduces a race condition, since other processes may change the file's state between the two calls. Instead, user code should open/read/write the file directly and handle the error raised if the file does not exist.

Perhaps, the pattern itself is wrong, and even access shouldn't be used here

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what you want to do is use await fs.writeFile(path, data, { flag: 'ax+' }) (see flags) and repeat that with a new suffix until the write succeeds.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's okay to use these methods in certain places (like a one-off operation, during initialization) that don't compromise the event loop.

@allansson's suggestion seems to handle both the check and the write at the same time so we can consider that approach as well.

If using fs.access, we could condering cleaning up the then/catch block if we move it to a helper function since it's been used in a few places.

const fileExists = await checkFileExsits(path.join(directory, uniqueFileName)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote the function to use writeFile

@@ -5,7 +5,7 @@ export function createEmptyRule(type: TestRule['type']): TestRule {
case 'correlation':
return {
type: 'correlation',
id: self.crypto.randomUUID(),
id: crypto.randomUUID(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, I believe it's only used by the state manager (so we know each one is selected/being edited etc). It might be easier to have an id on file than setting than during runtime 🤔

@@ -41,7 +37,10 @@ export function useUpdateValueInGeneratorFile(fileName: string) {
return useMutation({
mutationFn: async ({ key, value }: { key: string; value: unknown }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing to do with this PR really, but just an idea: instead of passing a key and value to update (which doesn't seem to be type safe?), why not use a map function:

useUpdateInFile(fileName: (generator: Generator) => {
  return {
    ...generator,
    myValue: "hello"
  }
})

src/main.ts Outdated
Comment on lines 1002 to 1004
let fileExists = await access(path.join(directory, uniqueFileName))
.then(() => true)
.catch(() => false)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it's okay to use these methods in certain places (like a one-off operation, during initialization) that don't compromise the event loop.

@allansson's suggestion seems to handle both the check and the write at the same time so we can consider that approach as well.

If using fs.access, we could condering cleaning up the then/catch block if we move it to a helper function since it's been used in a few places.

const fileExists = await checkFileExsits(path.join(directory, uniqueFileName)

Llandy3d
Llandy3d previously approved these changes Feb 7, 2025
Copy link
Member

@Llandy3d Llandy3d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 🙌

Copy link
Collaborator

@allansson allansson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really like this change. Remove much of the clutter in the sidebar and it overall just feel more organized now.

Great job! 💯

@going-confetti going-confetti merged commit 445202e into main Feb 10, 2025
3 checks passed
@going-confetti going-confetti deleted the feat/simpler-autogenerated-names branch February 10, 2025 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants