-
Notifications
You must be signed in to change notification settings - Fork 24
Issue #251045 feat: Hierarchical Categories Implementation #668
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
base: develop
Are you sure you want to change the base?
Changes from all commits
e9cf645
554d27a
030f146
ceb9598
173caa3
5aa79a8
a77b037
a6b6f27
785dbe9
57e3bd3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,7 +36,7 @@ module.exports = class LibraryCategories extends Abstract { | |
| } | ||
|
|
||
| /** | ||
| * @api {get} /improvement-project/api/v1/library/categories/projects/:categoryExternalId?page=:page&limit=:limit&search=:search&sort=:sort | ||
| * @api {get} /improvement-project/api/v1/library/categories/projects/:categoryExternalId?page=:page&limit=:limit&search=:search&sort=:sort | ||
| * List of library projects. | ||
| * @apiVersion 1.0.0 | ||
| * @apiGroup Library Categories | ||
|
|
@@ -56,7 +56,7 @@ module.exports = class LibraryCategories extends Abstract { | |
| "description" : "Test template description", | ||
| "createdAt": "2020-08-31T05:59:12.230Z" | ||
| } | ||
| ], | ||
| ], | ||
| "count": 7 | ||
| } | ||
| } | ||
|
|
@@ -179,7 +179,7 @@ module.exports = class LibraryCategories extends Abstract { | |
| } | ||
|
|
||
| /** | ||
| * @api {get} /improvement-project/api/v1/library/categories/list | ||
| * @api {get} /improvement-project/api/v1/library/categories/list | ||
| * List of library categories. | ||
| * @apiVersion 1.0.0 | ||
| * @apiGroup Library Categories | ||
|
|
@@ -240,7 +240,66 @@ module.exports = class LibraryCategories extends Abstract { | |
| async list(req) { | ||
| return new Promise(async (resolve, reject) => { | ||
| try { | ||
| let projectCategories = await libraryCategoriesHelper.list(req) | ||
| let projectCategories = await libraryCategoriesHelper.list(req.searchText, req.query, req.userDetails) | ||
|
|
||
| projectCategories.result = projectCategories.data | ||
|
|
||
| return resolve(projectCategories) | ||
| } catch (error) { | ||
| return reject({ | ||
| status: error.status || HTTP_STATUS_CODE.internal_server_error.status, | ||
| message: error.message || HTTP_STATUS_CODE.internal_server_error.message, | ||
| errorObject: error, | ||
| }) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * delete a library category | ||
| * @method | ||
| * @name delete | ||
| * @param {Object} req - requested data | ||
| * @returns {Array} Library categories. | ||
| */ | ||
|
|
||
|
vaivk369 marked this conversation as resolved.
|
||
| async delete(req) { | ||
| return new Promise(async (resolve, reject) => { | ||
| try { | ||
| const filterQuery = { | ||
| _id: req.params._id, | ||
| } | ||
| let projectCategories = await libraryCategoriesHelper.delete(filterQuery, req.userDetails) | ||
|
vaivk369 marked this conversation as resolved.
|
||
|
|
||
| projectCategories.result = projectCategories.data | ||
|
|
||
| return resolve(projectCategories) | ||
| } catch (error) { | ||
| return reject({ | ||
| status: error.status || HTTP_STATUS_CODE.internal_server_error.status, | ||
| message: error.message || HTTP_STATUS_CODE.internal_server_error.message, | ||
| errorObject: error, | ||
| }) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * read a library category | ||
| * @method | ||
| * @name details | ||
| * @param {Object} req - requested data | ||
| * @returns {Array} Library categories. | ||
| */ | ||
|
|
||
| async details(req) { | ||
| return new Promise(async (resolve, reject) => { | ||
| try { | ||
| const filterQuery = { | ||
| _id: req.params._id, | ||
| getChildren: req.query.getChildren === 'true', | ||
| } | ||
| let projectCategories = await libraryCategoriesHelper.details(filterQuery, req.userDetails) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vaivk369 why do we need user detail to get category details?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @aks30 to return category only if its tenantId = user's tenant Id t |
||
|
|
||
| projectCategories.result = projectCategories.data | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| /** | ||
| * Migration: addHierarchyFieldsToCategories | ||
| * Description: Add hierarchical fields to existing project categories | ||
| * Fields added: | ||
| * - parentId: ObjectId (null by default) | ||
| * - hasChildCategories: Boolean (false by default) | ||
| * - sequenceNumber: Number (0 by default) | ||
| * - metaInformation: Object ({} by default) | ||
| */ | ||
|
|
||
| const mongoose = require('mongoose') | ||
|
|
||
| module.exports = { | ||
| async up(db, client) { | ||
| const session = client.startSession() | ||
| try { | ||
| await session.withTransaction(async () => { | ||
| const categoriesCollection = db.collection('projectCategories') | ||
|
|
||
| // Update all existing documents to add the new fields with default values | ||
| // Only update documents where parentId doesn't exist (for idempotency) | ||
| const result = await categoriesCollection.updateMany( | ||
| { | ||
| parentId: { $exists: false }, | ||
| }, | ||
| { | ||
| $set: { | ||
| parentId: null, | ||
| hasChildCategories: false, | ||
| sequenceNumber: 0, | ||
| metaInformation: {}, | ||
| }, | ||
| }, | ||
| { session } | ||
| ) | ||
|
|
||
| console.log( | ||
| `Migration: addHierarchyFieldsToCategories - Updated ${result.modifiedCount} category documents` | ||
| ) | ||
|
|
||
| // Create indexes if they don't exist | ||
| await categoriesCollection.createIndex({ parentId: 1, tenantId: 1 }, { session }) | ||
| console.log('Created compound index on parentId and tenantId') | ||
|
|
||
| return result | ||
| }) | ||
| } catch (error) { | ||
| console.error('Error during migration addHierarchyFieldsToCategories:', error) | ||
| throw error | ||
| } finally { | ||
| await session.endSession() | ||
| } | ||
| }, | ||
|
|
||
| async down(db, client) { | ||
| const session = client.startSession() | ||
| try { | ||
| await session.withTransaction(async () => { | ||
| const categoriesCollection = db.collection('projectCategories') | ||
|
|
||
| // Remove the added fields from all documents | ||
| const result = await categoriesCollection.updateMany( | ||
| {}, | ||
| { | ||
| $unset: { | ||
| parentId: '', | ||
| hasChildCategories: '', | ||
| sequenceNumber: '', | ||
| metaInformation: '', | ||
| }, | ||
| }, | ||
| { session } | ||
| ) | ||
|
|
||
| console.log( | ||
| `Migration rollback: addHierarchyFieldsToCategories - Reverted ${result.modifiedCount} category documents` | ||
| ) | ||
|
|
||
| // Drop the compound index if it exists | ||
| try { | ||
| await categoriesCollection.dropIndex('parentId_1_tenantId_1', { session }) | ||
| console.log('Dropped compound index on parentId and tenantId') | ||
| } catch (err) { | ||
| // Index might not exist, which is fine | ||
| console.log('Compound index not found during rollback') | ||
| } | ||
|
|
||
| return result | ||
| }) | ||
| } catch (error) { | ||
| console.error('Error during migration rollback addHierarchyFieldsToCategories:', error) | ||
| throw error | ||
| } finally { | ||
| await session.endSession() | ||
| } | ||
| }, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,11 +66,44 @@ module.exports = { | |
| default: [], | ||
| index: true, | ||
| }, | ||
| description: { | ||
| type: String, | ||
| required: true, | ||
| default: 'default', | ||
| }, | ||
| keywords: { | ||
| type: Array, | ||
| default: [], | ||
| index: true, | ||
| }, | ||
| parentId: { | ||
| type: 'ObjectId', | ||
| default: null, | ||
| index: true, // CRITICAL for hierarchy queries | ||
| }, | ||
| hasChildCategories: { | ||
| type: Boolean, | ||
| default: false, | ||
| index: true, // Quick leaf identification | ||
| }, | ||
| sequenceNumber: { | ||
| type: Number, | ||
| default: 0, | ||
| index: true, | ||
| }, | ||
|
vaivk369 marked this conversation as resolved.
|
||
| metaInformation: { | ||
| type: Object, | ||
| default: {}, | ||
| }, | ||
| }, | ||
| compoundIndex: [ | ||
| { | ||
| name: { externalId: 1, tenantId: 1 }, | ||
| indexType: { unique: true }, | ||
| }, | ||
| // For Query (parentId + tenantId queries) | ||
| { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vaivk369 Not sure what is the use of these compound indexes, can you highlight please
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For Query (parentId + tenantId queries) @aks30 |
||
| name: { parentId: 1, tenantId: 1 }, | ||
| }, | ||
| ], | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,6 @@ module.exports = { | |
| type: String, | ||
| index: true, | ||
| }, | ||
| name: String, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vaivk369 Any reason to remove this?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if category name is updated, the name in project-templates become stale and then we would need to write sync job to update the names in project templates and projects. As we are alredy haaving id and external Id of the category, we can safely remove name. |
||
| }, | ||
| ], | ||
| description: { | ||
|
|
||
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.
@vaivk369 why do we need user detail to get category listing?
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.
we should get list of categories for the tenant user is belonging to right?
user categories model has tenant.
Also we should fetch categories which are having visibleToOrganizations set to orgId of the user
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.
@aks30