Skip to content

ISSUE #5299 - Ticket Filters in Tabular View #5539

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

Open
wants to merge 68 commits into
base: ISSUE_5545
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
517f7f4
ISSUE #5299 - first pass at filters in tabular view
The-Daniel Apr 23, 2025
1c001dc
ISSUE #5299 - oneOf/ManyOf filters now work in tabular view
The-Daniel May 6, 2025
c6f7d6e
ISSUE #5299 - remove search input
The-Daniel May 6, 2025
193675f
ISSUE #5299 - add new filter button
The-Daniel May 6, 2025
de6fe83
ISSUE #5299 - preset filters are added to tabular view
The-Daniel May 7, 2025
ba506c8
ISSUE #5299 - styling tweaks
The-Daniel May 8, 2025
19795bb
ISSUE #5299 - only fetch filtered tickets after list has loaded
The-Daniel May 8, 2025
f806f5a
ISSUE #5299 - add loading spinner to viewer tickets list until initia…
The-Daniel May 9, 2025
5980bd0
Merge branch 'staging' into ISSUE_5299
The-Daniel May 9, 2025
1689d6f
ISSUE #5299 - update tests
The-Daniel May 9, 2025
ede9862
ISSUE #5299 - remove template id option when filtering in tabular view
The-Daniel May 9, 2025
d86e01b
ISSUE #5299 - move pendingInitialFilters action and selector
The-Daniel May 9, 2025
014b3d5
ISSUE #5299 - add test for initial ticket pending status
The-Daniel May 9, 2025
551e947
ISSUE #5299 - in tabular view the initial status filters are only fro…
The-Daniel May 9, 2025
dbae321
ISSUE #5299 - update filtered tickets list when ticket is created/edited
The-Daniel May 13, 2025
e589f9c
ISSUE #5299 - fix crash due to hook order when refreshing in tabular …
The-Daniel May 13, 2025
49c1034
ISSUE #5299 - fix pins not showing when landing on details card. (Tem…
The-Daniel May 13, 2025
d04022d
ISSUE #5299 - fix multiple bugs because tickets were not fetched when…
The-Daniel May 13, 2025
7794c5e
ISSUE #5299 - simplify usage of models in tickets table
The-Daniel May 13, 2025
5ff5788
ISSUE #5299 - fix typescript problem
The-Daniel May 13, 2025
6a1657a
ISSUE #5299 - actually fix typescript issue this time
The-Daniel May 14, 2025
bbaedc8
ISSUE #5375 - revert changes
Amantini1997 Feb 25, 2025
0647151
Merge branch 'staging' into ISSUE_5299
The-Daniel May 16, 2025
7159b4e
ISSUE #5299 - template is passed as a prop to default filters setter
The-Daniel May 19, 2025
96d3ed2
ISSUE #5299 - pass unusedFilters to the filter selection as a prop
The-Daniel May 19, 2025
44590ab
ISSUE #5299 - simplify selectOptions logic
The-Daniel May 19, 2025
4a54756
ISSUE #5299 - use useState() so selectors are memoised
The-Daniel May 20, 2025
09a4fb7
ISSUE #5299 - use Promise.all instead of weird async reduce
The-Daniel May 20, 2025
f13035f
ISSUE #5299 - create useSelectedModelsIds hook
The-Daniel May 21, 2025
ddd8664
ISSUE #5299 - fix repeated key warning
The-Daniel May 21, 2025
825d660
ISSUE #5299 - improve how filtered tickets are fetched and used
The-Daniel May 21, 2025
d647b77
ISSUE #5299 - use useSelectedModelIds in filter form values
The-Daniel May 21, 2025
d381ecf
ISSUE #5299 - move available template ids to redux
The-Daniel May 21, 2025
9717d1a
ISSUE #5299 - fix range 'to' not being vertically centred
The-Daniel May 21, 2025
2e4e6cf
Merge branch 'staging' into ISSUE_5299
The-Daniel May 21, 2025
b10eca7
Merge branch 'ISSUE_5299' of https://github.com/3drepo/3drepo.io into…
The-Daniel May 21, 2025
f8aabbd
ISSUE #5299 - change availableTemplatesIds to be filterabletemplatesI…
The-Daniel May 22, 2025
bf88228
ISSUE #5299 - remove pending filters state. fetchFilteredTickets is c…
The-Daniel May 22, 2025
b0700f7
ISSUE #5299 - add test for filterabletemplateIds
The-Daniel May 22, 2025
b3d6549
Merge branch 'staging' into ISSUE_5299
The-Daniel May 22, 2025
1572d5c
Merge branch 'staging' into ISSUE_5299
The-Daniel May 27, 2025
d37571d
Merge branch 'staging' into ISSUE_5299
The-Daniel May 29, 2025
1fcfaaa
ISSUE #5299 - merge cleanup. Default filters setter works in tabular …
The-Daniel May 29, 2025
9d6ee3c
ISSUE #5299 - prevent unnecessary fetchFilteredTickets api calls
The-Daniel May 29, 2025
f9538db
Merge branch 'staging' into ISSUE_5299
The-Daniel May 29, 2025
f288796
Merge branch 'staging' into ISSUE_5299
The-Daniel Jun 6, 2025
bd80519
ISSUE #5299 - fix create ticket button always disabled
The-Daniel Jun 9, 2025
b9b22f0
ISSUE #5299 - add comment
The-Daniel Jun 10, 2025
5e4e70c
ISSUE #5299 - fix typo
The-Daniel Jun 10, 2025
aa2c1ff
ISSUE #5299 - give filter select property selector a more specific name
The-Daniel Jun 10, 2025
c9d2946
ISSUE #5299 - get filter property options from a helper instead of a …
The-Daniel Jun 10, 2025
4e2f901
ISSUE #5299 - revert to using all templates on model for getting filt…
The-Daniel Jun 10, 2025
6b1894b
ISSUE #5299 - ticket table controls and footer are sticky
The-Daniel Jun 11, 2025
1b68e76
ISSUE #5299 - move default ticket filter setter into a hook
The-Daniel Jun 12, 2025
403444b
ISSUE #5299 - assignees filter accepts from all models. assignee sele…
The-Daniel Jun 12, 2025
1fdd146
Merge branch 'ISSUE_5299' of https://github.com/3drepo/3drepo.io into…
The-Daniel Jun 12, 2025
e003fd3
Merge branch 'staging' into ISSUE_5299
The-Daniel Jun 12, 2025
5a1d643
Merge branch 'ISSUE_5299' of https://github.com/3drepo/3drepo.io into…
The-Daniel Jun 12, 2025
6cb2a2d
ISSUE #5299 - fix default sttus filter including invalid statuses
The-Daniel Jun 27, 2025
142553f
ISSUE #5299 - reset filters when exitting and reentering tabular view
The-Daniel Jun 27, 2025
0821c96
ISSUE #5299 - remove invalid filters when changing template
The-Daniel Jul 1, 2025
c23044c
ISSUE #5299 - remove duplicate useEffect (other is in tickets.hooks)
The-Daniel Jul 1, 2025
e56514d
ISSUE #5299 - status filter value does not show duplicates or unavail…
The-Daniel Jul 2, 2025
f29e244
ISSUE #5299 - deprecated properties do not show up in tabular view
The-Daniel Jul 2, 2025
025a819
ISSUE #5299 - default filters refresh when changing template in tabul…
The-Daniel Jul 2, 2025
99ca0cd
ISSUE #5299 - prevent crash when opening a federation with a ticketId…
The-Daniel Jul 2, 2025
9968e73
Merge branch 'ISSUE_5545' into ISSUE_5299
The-Daniel Jul 10, 2025
2055208
ISSUE #5299 - clean up merge
The-Daniel Jul 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"engines": {
"node": "22.x.x"
},
"apidoc": {
"title": "3D Repo V4 Backend documentation",
"name": "3D Repo V4 backend documentation",
"description": "Backend documentation for v4 APIs. To view v5 API endpoints, please visition https://www.3drepo.io/docs"
},
"private": true,
"scripts": {
"gen-docs": "yarn apidoc -i src/v4/routes/ -o docs/ ",
Expand All @@ -29,7 +34,7 @@
"@elastic/elasticsearch": "7.17.14",
"@frontegg/client": "5.3.2",
"amqplib": "0.10.5",
"apidoc": "0.29.0",
"apidoc": "1.2.0",
"app-config": "1.0.1",
"archiver": "6.0.2",
"archiver-zip-encrypted": "2.0.0",
Expand Down Expand Up @@ -59,13 +64,13 @@
"moment": "2.30.1",
"moment-timezone": "0.5.45",
"mongodb": "3.7.4",
"multer": "2.0.0",
"multer": "2.0.1",
"node-device-detector": "2.2.0",
"nodemailer": "6.9.14",
"openapi-schema-validator": "12.1.3",
"paypal-rest-sdk": "1.8.1",
"pug": "3.0.3",
"serialize-javascript": "4.0.0",
"serialize-javascript": "6.0.2",
"serve-favicon": "2.5.0",
"sharp": "0.33.5",
"slash": "3.0.0",
Expand Down Expand Up @@ -118,7 +123,8 @@
"wrap-ansi": "6.2.0",
"cross-spawn": "7.0.6",
"micromatch": "4.0.8",
"nanoid": "3.3.8"
"nanoid": "3.3.8",
"brace-expansion": "2.0.2"
},
"description": "The backend for 3drepo.io",
"main": "3drepo.js",
Expand Down
36 changes: 27 additions & 9 deletions backend/src/scripts/utility/teamspaces/createTeamspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,28 @@
*/

const Path = require('path');
const { v5Path } = require('../../../interop');
const { v5Path, v4Path } = require('../../../interop');

const { logger } = require(`${v5Path}/utils/logger`);

const { initTeamspace } = require(`${v5Path}/processors/teamspaces`);
const { getUserByUsername } = require(`${v5Path}/models/users`);
const { getUserByUsernameOrEmail } = require(`${v5Path}/models/users`);
const { getTeamspaceSetting } = require(`${v5Path}/models/teamspaceSettings`);
const { templates } = require(`${v5Path}/utils/responseCodes`);
const { create: createInvite } = require(`${v4Path}/models/invitations`);

const run = async (teamspace, user) => {
logger.logInfo(`Checking ${user} exists...`);
await getUserByUsername(user);
const { DEFAULT_OWNER_JOB } = require(`${v5Path}/models/jobs.constants`);

const run = async (teamspace, user, accountId) => {
logger.logInfo(`Checking ${user} information...`);
let username;
try {
const { user: foundUser } = await getUserByUsernameOrEmail(user);
username = foundUser;
} catch (error) {
/* istanbul ignore next */
if (error.message !== templates.userNotFound.message) throw error;
}

logger.logInfo(`Checking if teamspace ${teamspace} already exists...`);
const teamspaceExists = await getTeamspaceSetting(teamspace, { _id: 1 }).catch(() => false);
Expand All @@ -35,10 +46,13 @@ const run = async (teamspace, user) => {
throw new Error('Teamspace already exists');
}

await initTeamspace(teamspace, user);
await initTeamspace(teamspace, username, accountId);
if (!username) {
await createInvite(user, teamspace, DEFAULT_OWNER_JOB, undefined, { teamspace_admin: true }, false);
}

logger.logInfo(`Teamspace ${teamspace} created.`);
};

const genYargs = /* istanbul ignore next */(yargs) => {
const commandName = Path.basename(__filename, Path.extname(__filename));
const argsSpec = (subYargs) => subYargs.option('teamspace',
Expand All @@ -51,11 +65,15 @@ const genYargs = /* istanbul ignore next */(yargs) => {
describe: 'a user to be assigned to be an admin of this teamspace',
type: 'string',
demandOption: true,
});
}).option('accountId', {
describe: 'an already existing frontEgg account Id (tennant Id) for the teamspace',
type: 'string',
demandOption: false,
});
return yargs.command(commandName,
'Create a teamspace of the name provided and gives the user specified admin privileges',
argsSpec,
({ teamspace, user }) => run(teamspace, user));
({ teamspace, user, accountId }) => run(teamspace, user, accountId));
};

module.exports = {
Expand Down
21 changes: 15 additions & 6 deletions backend/src/v4/models/invitations.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,17 @@ const cleanPermissions = (permissions) => {

const sendInvitationEmail = async (email, username, teamspace) => {
const refId = await getTeamspaceRefId(teamspace);
const { customData: { firstName, lastName }} = await User.findByUserName(username, { "customData.firstName": 1, "customData.lastName": 1});
const sender = [firstName, lastName].join(" ");

let sender;
if(username) {
const { customData: { firstName, lastName }} = await User.findByUserName(username, { "customData.firstName": 1, "customData.lastName": 1});
sender = [firstName, lastName].join(" ");
}

await addUserToAccount(refId, email, undefined, {teamspace, sender });
};

invitations.create = async (email, teamspace, job, username, permissions = {}) => {
invitations.create = async (email, teamspace, job, username, permissions = {}, checkLicenceLimit = true) => {
// 1 - find if there is already and invitation with that email
// 2 - if there is update the invitation with the new teamspace data
// 2.5 - if there is not, create an entry with that email and job/permission
Expand Down Expand Up @@ -138,6 +142,9 @@ invitations.create = async (email, teamspace, job, username, permissions = {}) =

// if its a new teamspace that the user has been invited send an invitation email
if (result.teamSpaces.every(t=> t.teamspace !== teamspace)) {
if(checkLicenceLimit) {
await User.hasReachedLicenceLimitCheck(teamspace);
}
await sendInvitationEmail(email, username, teamspace);
publish(events.INVITATION_ADDED, { teamspace, executor: username, email, job, permissions});
}
Expand All @@ -148,7 +155,9 @@ invitations.create = async (email, teamspace, job, username, permissions = {}) =
await coll.updateOne({_id:email}, { $set: invitation });

} else {
await User.hasReachedLicenceLimitCheck(teamspace);
if(checkLicenceLimit) {
await User.hasReachedLicenceLimitCheck(teamspace);
}
const invitation = {_id:email ,teamSpaces: [teamspaceEntry] };
await sendInvitationEmail(email, username, teamspace);
await coll.insertOne(invitation);
Expand Down Expand Up @@ -237,13 +246,13 @@ const applyTeamspacePermissions = (invitedUser) => async ({ teamspace, job, perm
const teamPerms = permissions.teamspace_admin ? ["teamspace_admin"] : [];

try {
await User.addTeamMember(teamspace, invitedUser, job, teamPerms);
await User.addTeamMember(teamspace, invitedUser, job, teamPerms, undefined, true);

if (!permissions.teamspace_admin) {
await Promise.all(permissions.projects.map(applyProjectPermissions(teamspace, invitedUser)));
}
} catch(err) {
systemLogger.logError("Something failed when unpacking invitation: " + err.stack);
systemLogger.logError("Something failed when unpacking invitation: ", err.message);
}
};

Expand Down
7 changes: 4 additions & 3 deletions backend/src/v4/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -526,9 +526,10 @@ User.removeTeamMember = async function (teamspace, userToRemove, cascadeRemove,

};

User.addTeamMember = async function(teamspace, userToAdd, job, permissions, executor) {
await hasReachedLicenceLimit(teamspace);

User.addTeamMember = async function(teamspace, userToAdd, job, permissions, executor, bypassQuotaCheck = false) {
if(!bypassQuotaCheck) {
await hasReachedLicenceLimit(teamspace);
}
let userEntry = null;
if (strings.email.isValidSync(userToAdd)) { // if the submited username is the email
userEntry = await User.findByEmail(userToAdd);
Expand Down
12 changes: 6 additions & 6 deletions backend/src/v4/routes/accountPermission.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
* @apiDescription Assign account level permission to a user
*
* @apiParam {String} teamspace Name of teamspace
* @apiParam (Request body) {String} user User to assign permissions to
* @apiParam (Request body) {String[]} permissions List of account level permissions
* @apiBody {String} user User to assign permissions to
* @apiBody {String[]} permissions List of account level permissions
* @apiSuccess (200) {String} user User
* @apiSuccess (200) {String[]} permissions Account Level Permission types
*
Expand Down Expand Up @@ -101,8 +101,8 @@
*
* @apiParam {String} teamspace Name of teamspace
* @apiParam {String} user User to update
* @apiParam (Request body) {String[]} permissions List of account level permissions
* @apiSuccess {String[]} permissions List of account level permissions
* @apiBody {String[]} permissions List of account level permissions
* @apiSuccess (200) {String[]} permissions List of account level permissions
*
* @apiExample {put} Example usage:
* PUT /acme/permissions/alice HTTP/1.1
Expand All @@ -112,7 +112,7 @@
* ]
* }
*
* @apiSuccessExample {json} Success-Response
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "permissions": [
Expand All @@ -134,7 +134,7 @@
* @apiExample {delete} Example usage:
* DELETE /acme/permissions/alice HTTP/1.1
*
* @apiSuccessExample {json} Success-Response
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {}
*/
Expand Down
3 changes: 0 additions & 3 deletions backend/src/v4/routes/apidoc.json

This file was deleted.

44 changes: 23 additions & 21 deletions backend/src/v4/routes/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ const { fileExists } = require("../models/fileRef");
* @apiDescription Show current application version.
*
* @apiSuccess (200) {String} VERSION API service version
* @apiSuccess (200) {String} unity Unity viewer version
* @apiSuccess (200) {String} navis Autodesk Navisworks version
* @apiSuccess (200) {String} unitydll Unity viewer version
* @apiSuccess (200) {Object} unity Unity viewer version
* @apiSuccess (200) {Object} navis Autodesk Navisworks version
* @apiSuccess (200) {Object} unitydll Unity viewer version
*
* @apiSuccessExample {json} Success-Response
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "VERSION": "2.20.1",
Expand All @@ -69,24 +69,24 @@ const { fileExists } = require("../models/fileRef");
router.get("/version", printVersion);

/**
* @api {get} /:user.json List account information
* @api {get} /:account.json List account information
* @apiName listInfo
* @apiGroup Account
* @apiDescription Account information and list of projects grouped by teamspace
* that the user has access to.
*
* @apiParam {String} user User
* @apiParam {String} account.json Account name with .json extension
* @apiSuccess (200) {Object[]} accounts User account
* @apiSuccess (200) {Object} billingInfo Billing information
* @apiSuccess (200) {String} email User e-mail address
* @apiSuccess (200) {String} firstName First name
* @apiSuccess (200) {String} lastName Surname
* @apiSuccess (200) {Boolean} hasAvatar True if user account has an avatar
*
* @apiExample {delete} Example usage:
* @apiExample {get} Example usage:
* GET /alice.json HTTP/1.1
*
* @apiSuccessExample {json} Success-Response
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "accounts": [
Expand Down Expand Up @@ -220,7 +220,7 @@ router.get("/version", printVersion);
* "_id": "Actor"
* },
* {
* "_id": "Producer
* "_id": "Producer"
* }
* ]
* }
Expand All @@ -229,23 +229,25 @@ router.get("/:account.json", middlewares.loggedIn, listInfo);
// TODO: divide into different endpoints that makes sense.

/**
* @api {get} /:user/avatar Get avatar
* @api {get} /:account/avatar Get avatar
* @apiName getAvatar
* @apiGroup Account
* @apiDescription Get user avatar.
*
* @apiParam {String} user User
* @apiSuccess (200) {Object} avatar User Avatar Image
* @apiParam {String} account Account name
* @apiSuccess (200) {File} avatar User Avatar Image
* @apiError (404) USER_DOES_NOT_HAVE_AVATAR User does not have an avatar
*
* @apiExample {put} Example usage:
* @apiExample {get} Example usage:
* GET /alice/avatar HTTP/1.1
*
* @apiSuccessExample {json} Success-Response
* @apiSuccessExample {binary} Success-Response:
* HTTP/1.1 200 OK
* <binary image>
* Content-Type: image/png
*
* <binary image data>
*
* @apiErrorExample {json} Error-Response
* @apiErrorExample {json} Error-Response:
* HTTP/1.1 404 Not Found
* {
* "message": "User does not have an avatar",
Expand All @@ -257,16 +259,16 @@ router.get("/:account.json", middlewares.loggedIn, listInfo);
router.get("/:account/avatar", middlewares.loggedIn, getAvatar);

/**
* @api {post} /:user/avatar Upload avatar
* @api {post} /:account/avatar Upload avatar
* @apiName uploadAvatar
* @apiGroup Account
* @apiDescription Upload a new avatar image.
* Only multipart form data content type will be accepted.
*
* @apiParam {String} user User
* @apiParam (Request body) {File} file Image to upload
* @apiParam {String} account Account name
* @apiBody {File} file Image to upload
*
* @apiExample {put} Example usage:
* @apiExample {post} Example usage:
* POST /alice/avatar HTTP/1.1
* Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryN8dwXAkcO1frCHLf
*
Expand All @@ -278,7 +280,7 @@ router.get("/:account/avatar", middlewares.loggedIn, getAvatar);
* ------WebKitFormBoundaryN8dwXAkcO1frCHLf--
*
* @apiSuccess (200) {Object} status Status of Avatar upload.
* @apiSuccessExample {json} Success-Response
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* "status":"success"
Expand Down
Loading
Loading