-
-
Notifications
You must be signed in to change notification settings - Fork 115
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(api): Update slug generation method #420
Conversation
PR Reviewer Guide 🔍
|
PR Code Suggestions ✨
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey man, I'm sorry but still this isn't what I wanted/meant. Please read carefully through the issue description. We don't want a while loop or any loops for that matter of fact. It will be a simple if else statement. Also, we don't want to generate slug names including the entity type. Entity types are used to assess the uniqueness of the slug in the table. You are just required to update the function listed in the issue, rest of the things are as per requirements.
@rajdip-b Is this the one you are mentioning then? 👇 const generateSlug = async (name: string,entityType:string,prisma: PrismaService): Promise<string> => {
// Convert to lowercase
const lowerCaseName = name.trim().toLowerCase()
// Replace spaces with hyphens
const hyphenatedName = lowerCaseName.replace(/\s+/g, '-')
// Replace all non-alphanumeric characters with hyphens
const alphanumericName = hyphenatedName.replace(/[^a-zA-Z0-9-]/g, '-')
// Returns the camelCase version of the entity type (WORKSPACE_ROLE -> workspaceRole)
const entityTypeCamelCase = convertEntityTypeToCamelCase(entityType);
//Querying the table to get slug (if available)
const existingSlug=await prisma.entityTypeCamelCase.findMany({
where: {
slug: {
startsWith: `${alphanumericName}-`
}
},
orderBy: {
slug: 'desc'
}
});
if(existingSlug.length==0){
return `${alphanumericName}-0`;
}
//Returns the slug with the highest ASCII value
const lastSlug = existingSlug[0].slug;
const suffix=lastSlug.substring(alphanumericName.length+1);
const newSuffix=incrementSlugSuffix(suffix);
return `${alphanumericName}-${newSuffix}`;
} |
Also, please go through the prisma tables first. there isnt no table named as |
This function used for incrementing suffix received const incrementSlugSuffix = (suffix: string): string => {
const charset = '0123456789abcdefghijklmnopqrstuvwxyz';
if (!suffix) {
return '0';
}
let result = '';
let carry = true;
for (let i = suffix.length - 1; i >= 0; i--) {
if (carry) {
const currentChar = suffix[i];
const index = charset.indexOf(currentChar);
if (index === -1) {
throw new Error(`Invalid character in slug suffix: ${currentChar}`);
}
const nextIndex = (index + 1) % charset.length;
result = charset[nextIndex] + result;
// Carry over if we wrapped around to '0'
carry = nextIndex === 0;
} else {
// No carry, just append the remaining part of the suffix
result = suffix[i] + result;
}
}
if (carry) {
result = '0' + result;
}
return result;
}; This is just to produce the slug name const generateSlugName = (name: string): string => {
// Convert to lowercase
const lowerCaseName = name.trim().toLowerCase()
// Replace spaces with hyphens
const hyphenatedName = lowerCaseName.replace(/\s+/g, '-')
// Replace all non-alphanumeric characters with hyphens
const alphanumericName = hyphenatedName.replace(/[^a-zA-Z0-9-]/g, '-')
return alphanumericName
} The following part is to get list of slug containing the const getWorkspaceRoleIfSlugExists = async (
slug: Workspace['slug'],
prisma: PrismaService
): Promise<WorkspaceRole[]> => {
const existingSlug=await prisma.workspaceRole.findMany({
where: {
slug: {
startsWith: `${slug}-`
}
},
orderBy: {
slug: 'desc'
}
});
return existingSlug;
} Changes in export default async function generateEntitySlug(
name: string,
entityType:
| 'WORKSPACE_ROLE'
| 'WORKSPACE'
| 'PROJECT'
| 'VARIABLE'
| 'SECRET'
| 'INTEGRATION'
| 'ENVIRONMENT'
| 'API_KEY',
prisma: PrismaService
): Promise<string> {
//Removing while loop
const baseSlug=generateSlugName(name)
switch (entityType) {
case 'WORKSPACE_ROLE':
const slugList=await getWorkspaceRoleIfSlugExists(baseSlug,prisma)
let suffix=""
if (slugList.length > 0) {
//This takes only the suffix part (not the slug name part)
suffix=slugList[0].slug.substring(baseSlug.length+1);
}
const newSuffix=incrementSlugSuffix(suffix)
return `${baseSlug}-${newSuffix}`
... Showing only for Is this the one you are looking for? |
2587d65
to
f68dd69
Compare
Yes, I think this will suffice. Although, do note that the current format of your code has a lot of redundancy. Ideally we would like to get rid of that. For example, instead of making the if check in the switch cases, you can have just one switch check in |
f68dd69
to
25178d1
Compare
Ah please don't push that now. I have not updated that thing in my code. I have just shown you my Ideation. Since things are fine, I will push the current change in the code. After that you can push/merge as per your needs. |
Ah boy, my bad! Although, there shouldn't be any conflicts with your code. I have made changes to other places. |
For this one I think this would do. //Will define type or interface for the slugList types
const incrementSlugSuffix = (slugList:Workspace[]|WorkspaceRole[],baseSlug:string): string => {
const charset = '0123456789abcdefghijklmnopqrstuvwxyz';
let suffix = '';
if(slugList.length>0){
suffix = slugList[0].slug.substring(baseSlug.length + 1);
}
if (!suffix) {
return `${baseSlug}-0`;
}
let result = '';
let carry = true;
for (let i = suffix.length - 1; i >= 0; i--) {
if (carry) {
const currentChar = suffix[i];
const index = charset.indexOf(currentChar);
if (index === -1) {
throw new Error(`Invalid character in slug suffix: ${currentChar}`);
}
const nextIndex = (index + 1) % charset.length;
result = charset[nextIndex] + result;
// Carry over if we wrapped around to '0'
carry = nextIndex === 0;
} else {
// No carry, just append the remaining part of the suffix
result = suffix[i] + result;
}
}
if (carry) {
result = '0' + result;
}
return `${baseSlug}-${result}`;
}; Then the export default async function generateEntitySlug(
name: string,
entityType:
| 'WORKSPACE_ROLE'
| 'WORKSPACE'
| 'PROJECT'
| 'VARIABLE'
| 'SECRET'
| 'INTEGRATION'
| 'ENVIRONMENT'
| 'API_KEY',
prisma: PrismaService
): Promise<string> {
//Removing while loop
const baseSlug=generateSlugName(name)
switch (entityType) {
case 'WORKSPACE_ROLE':
const slugList=await getWorkspaceRoleIfSlugExists(baseSlug,prisma)
return incrementSlugSuffix(slugList,baseSlug)
//other cases |
Yes, additionally, you can replace the type of slugList from const existingSlug=await prisma.workspaceRole.findMany({
where: {
slug: {
startsWith: `${slug}-`
}
},
orderBy: {
slug: 'desc'
}
}); And also, in this query, you can select only the first element using P.S. please take care of the spaces and newlines. Use |
Can you also include a few tests? Unit tests would be fine. You would just need to mock the prisma client to send back dummy response. We would want to ensure that the changes in this file stay consistent. |
Ok let me try |
describe('generateEntitySlug for each entity type', () => {
it('should generate a unique slug for WORKSPACE_ROLE', async () => {
const mockPrismaResult = [
{
id: 'role-id',
slug: 'workspace-role-0',
workspaceId: 'workspace-id'
}
]
prismaSingleton.workspaceRole.findMany.mockResolvedValue(mockPrismaResult)
const slug = await generateEntitySlug(
'Workspace Role',
'WORKSPACE_ROLE',
prisma
)
expect(slug).toBe('workspace-role-1')
}) This always giving me error in mockPrismaResult. Why so? Do we require entire object will all the fields or few with |
You would need to override each of the |
I tried import { PrismaService } from '@/prisma/prisma.service'
import generateEntitySlug, {
generateSlugName,
incrementSlugSuffix
} from './slug-generator'
import { prismaSingleton } from '../../prisma-singleton'
import { Prisma, Workspace, WorkspaceRole } from '@prisma/client'
// Mock PrismaService
jest.mock('@/prisma/prisma.service')
describe('generateEntitySlug', () => {
let prisma: PrismaService
beforeEach(() => {
prisma = new PrismaService()
})
describe('generateSlugName', () => {
it('should convert name to slug format', () => {
expect(generateSlugName('Hello World')).toBe('hello-world')
expect(generateSlugName('Entity with 123')).toBe('entity-with-123')
expect(generateSlugName('Special #Name!')).toBe('special--name-')
})
})
describe('incrementSlugSuffix', () => {
it('should return base slug with `-0` when no suffix is found', () => {
const result = incrementSlugSuffix('', 'my-slug')
expect(result).toBe('my-slug-0')
})
it('should increment suffix when found', () => {
const result = incrementSlugSuffix('my-slug-0', 'my-slug')
expect(result).toBe('my-slug-1')
})
it('should handle complex increment cases with carryover', () => {
const result = incrementSlugSuffix('my-slug-z', 'my-slug')
expect(result).toBe('my-slug-00')
})
})
describe('generateEntitySlug for each entity type', () => {
it('should generate a unique slug for WORKSPACE_ROLE', async () => {
// Mocking the workspaceRole findMany method
// ;(prisma.workspaceRole.findMany as jest.Mock).mockResolvedValue([
// {
// slug: 'workspace-role-0'
// }
// ])
jest
.spyOn(prismaSingleton.workspaceRole, 'findMany')
.mockResolvedValue([
{ slug: 'workspace-role-0' }
] as Partial<WorkspaceRole>[])
const slug = await generateEntitySlug(
'Workspace Role',
'WORKSPACE_ROLE',
prisma
)
expect(slug).toBe('workspace-role-1')
})
})
}) Will this do any help prisma/prisma#7084 (comment) |
I think you can use the mock-deep package. It's already added to our deps: https://github.com/marchaos/jest-mock-extended |
I have been committing in a unconventional way. Everytime, I commit I get some errors using git cli. But I used the |
I'm not sure what issues you are facing since I have never heard about them. We do have some issues with failing tests while comitting which you can bypass using the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, I'll implement this once and then merge the code.
@Nil2000 I have updated your branch to use psql raw regex. The tests are failing due to the lack of mock. Once you implement that, we would be good to go. |
## [2.5.0](v2.4.0...v2.5.0) (2024-09-16) ### 🚀 Features * **api-client:** Added workspace controller ([#427](#427)) ([2f4edec](2f4edec)) * **api-client:** Added workspace role controller ([#430](#430)) ([b03ce8e](b03ce8e)) * **api-client:** Synced with latest API ([27f4309](27f4309)) * **api:** Add slug in entities ([#415](#415)) ([89e2fcc](89e2fcc)) * **api:** Included default workspace details in getSelf function ([#414](#414)) ([e67bbd6](e67bbd6)) * **platform:** Add loading skeleton in the [secure]s page ([#423](#423)) ([a97681e](a97681e)) * **schema:** Added a schema package ([01ea232](01ea232)) * **web:** Update about and careers page ([e167f53](e167f53)) ### 🐛 Bug Fixes * **api:** Error messages fixed in api-key service ([#418](#418)) ([edfbce0](edfbce0)) ### 📚 Documentation * Fixed minor typo in postman workspace link ([#411](#411)) ([ed23116](ed23116)) * Updated Postman links ([444bfb1](444bfb1)) ### 🔧 Miscellaneous Chores * **api:** Suppressed version check test in [secure] ([4688e8c](4688e8c)) * **api:** Update slug generation method ([#420](#420)) ([1f864df](1f864df)) ### 🔨 Code Refactoring * **API:** Refactor workspace-membership into a separate module ([#421](#421)) ([574170f](574170f)) * **platform:** added optional chaining due to strict null check ([#413](#413)) ([907e369](907e369))
🎉 This PR is included in version 2.5.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Co-authored-by: rajdip-b <agentR47@gmail.com>
## [2.5.0](keyshade-xyz/keyshade@v2.4.0...v2.5.0) (2024-09-16) ### 🚀 Features * **api-client:** Added workspace controller ([keyshade-xyz#427](keyshade-xyz#427)) ([2f4edec](keyshade-xyz@2f4edec)) * **api-client:** Added workspace role controller ([keyshade-xyz#430](keyshade-xyz#430)) ([b03ce8e](keyshade-xyz@b03ce8e)) * **api-client:** Synced with latest API ([27f4309](keyshade-xyz@27f4309)) * **api:** Add slug in entities ([keyshade-xyz#415](keyshade-xyz#415)) ([89e2fcc](keyshade-xyz@89e2fcc)) * **api:** Included default workspace details in getSelf function ([keyshade-xyz#414](keyshade-xyz#414)) ([e67bbd6](keyshade-xyz@e67bbd6)) * **platform:** Add loading skeleton in the [secure]s page ([keyshade-xyz#423](keyshade-xyz#423)) ([a97681e](keyshade-xyz@a97681e)) * **schema:** Added a schema package ([01ea232](keyshade-xyz@01ea232)) * **web:** Update about and careers page ([e167f53](keyshade-xyz@e167f53)) ### 🐛 Bug Fixes * **api:** Error messages fixed in api-key service ([keyshade-xyz#418](keyshade-xyz#418)) ([edfbce0](keyshade-xyz@edfbce0)) ### 📚 Documentation * Fixed minor typo in postman workspace link ([keyshade-xyz#411](keyshade-xyz#411)) ([ed23116](keyshade-xyz@ed23116)) * Updated Postman links ([444bfb1](keyshade-xyz@444bfb1)) ### 🔧 Miscellaneous Chores * **api:** Suppressed version check test in [secure] ([4688e8c](keyshade-xyz@4688e8c)) * **api:** Update slug generation method ([keyshade-xyz#420](keyshade-xyz#420)) ([1f864df](keyshade-xyz@1f864df)) ### 🔨 Code Refactoring * **API:** Refactor workspace-membership into a separate module ([keyshade-xyz#421](keyshade-xyz#421)) ([574170f](keyshade-xyz@574170f)) * **platform:** added optional chaining due to strict null check ([keyshade-xyz#413](keyshade-xyz#413)) ([907e369](keyshade-xyz@907e369))
User description
Description
Updated the
keyshade/apps/api/src/common/slug-generator.ts
Fixes #416
Dependencies
Mention any dependencies/packages used
Future Improvements
Mention any improvements to be done in future related to any file/feature
Mentions
Mention and tag the people
Screenshots of relevant screens
Add screenshots of relevant screens
Developer's checklist
If changes are made in the code:
Documentation Update
PR Type
Bug fix
Description
generateSlug
function to accept a counter parameter, improving the uniqueness of generated slugs.Changes walkthrough 📝
slug-generator.ts
Improve slug generation with counter for uniqueness
apps/api/src/common/slug-generator.ts
generateSlug
function to include a counter for uniqueness.characters.