Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c51e94c
Update CITATION.cff
doomlab Nov 14, 2025
604e877
add notification marker to deleted users
doomlab Dec 14, 2025
88652c9
fix double tooltip issue
doomlab Dec 14, 2025
5e8d1cd
fix scrolling in the table
doomlab Dec 14, 2025
e40a12b
form data problem solved!
doomlab Dec 15, 2025
7d476db
hide bars when it's NAN
doomlab Dec 15, 2025
9244ddd
fixed bug in task completion status
doomlab Dec 15, 2025
47a9dc6
hide role/tasks plot if no roles
doomlab Dec 15, 2025
1e49823
update internal viewer to not show things if they are no tasks/forms/…
doomlab Dec 15, 2025
d3516f2
fix visual issue on contributors showing html code
doomlab Dec 15, 2025
b22247a
remove blitz test that we didn't write
doomlab Dec 15, 2025
94998f4
fix test bugs created by updates
doomlab Dec 15, 2025
657653f
add mocks to make tests work
doomlab Dec 15, 2025
d77a7d4
skip password mutation for regular contributors
doomlab Dec 15, 2025
fd53b94
Merge pull request #488 from STAPLE-verse/487-anonymize-notifications
doomlab Dec 15, 2025
25ed7a5
Merge pull request #489 from STAPLE-verse/486-notes-sidebar
doomlab Dec 15, 2025
904e8ac
Merge pull request #490 from STAPLE-verse/484-overall-task-page
doomlab Dec 15, 2025
dce1e04
Merge pull request #491 from STAPLE-verse/482-project-summary-export
doomlab Dec 15, 2025
c33d5ac
Merge pull request #492 from STAPLE-verse/302-document-the-api-keys-n…
doomlab Dec 15, 2025
0c257c2
fixed filtering issue where if you were on a later page it wouldn't work
doomlab Dec 15, 2025
492d376
turn off global filter on dashboard
doomlab Dec 15, 2025
6dc61f5
fix date filtering
doomlab Dec 15, 2025
cd8efa0
fix date filtering and global search for read/unread
doomlab Dec 15, 2025
1610350
fix date filtering
doomlab Dec 15, 2025
5863d07
turn off global search on dashboard
doomlab Dec 15, 2025
6442875
add completed versus uncompleted
doomlab Dec 15, 2025
eea5717
adding date and text search filter updates
doomlab Dec 15, 2025
70d1191
delete unused table
doomlab Dec 15, 2025
54f1d3c
remove unused tables
doomlab Dec 15, 2025
82d7f60
add read/unread filter and fix dates
doomlab Dec 15, 2025
3a27cf5
update date and read/unread/completed filters
doomlab Dec 15, 2025
5e0b132
completed / not completed filtering
doomlab Dec 15, 2025
b5fd07a
updates to fix filtering
doomlab Dec 15, 2025
5743efc
Merge pull request #493 from STAPLE-verse/432-table-updates
doomlab Dec 15, 2025
5e0e1ca
fix duplicate issue
doomlab Dec 15, 2025
37ac851
Merge pull request #494 from STAPLE-verse/483-duplicate-tags
doomlab Dec 15, 2025
d4b6002
add a note about when you should see comment notifications
doomlab Dec 15, 2025
c66f64e
Merge pull request #495 from STAPLE-verse/485-overall-task-page-not-s…
doomlab Dec 15, 2025
c4aaf12
fix visual issue with project notifications
doomlab Dec 15, 2025
baef398
team update error bug
doomlab Dec 15, 2025
5961a2a
making labeling clear on team versus individual tasks
doomlab Dec 15, 2025
1535f60
fixing disconnect / deleting members and their notification hiccups
doomlab Dec 15, 2025
c0fa207
fix stupid coloring
doomlab Dec 15, 2025
6763c37
fix soft delete issue with middleware
doomlab Dec 15, 2025
129f980
add that forgotten note about the tags
doomlab Dec 15, 2025
8be855a
fix typing issue
doomlab Dec 15, 2025
c3024d4
Merge pull request #496 from STAPLE-verse/fix-notification-visuals
doomlab Dec 16, 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
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ preferred-citation:
- family-names: "Yedra"
given-names: "Engerst"
orcid: "https://orcid.org/0000-0002-9555-7148"
url: "https://github.com/STAPLE/STAPLE"
url: "https://github.com/STAPLE-verse/STAPLE"

contributors:
- family-names: "Hartgerink"
Expand Down
8 changes: 3 additions & 5 deletions db/middlewares/projectMemberMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ export default function projectMemberMiddleware(prisma) {
params.model === "ProjectMember" &&
(params.action === "findMany" || params.action === "findFirst")
) {
// Check if `deleted` is explicitly set to `undefined`
const hasExplicitUndefined =
"deleted" in params.args.where && params.args.where.deleted === undefined
const hasExplicitDeleted = "deleted" in (params.args.where || {})

if (!hasExplicitUndefined) {
if (!hasExplicitDeleted) {
params.args.where = {
...params.args.where,
deleted: false, // ✅ Always filter out soft-deleted members
deleted: false, // ✅ Always filter out soft-deleted members unless caller overrides
}
}
}
Expand Down
1 change: 1 addition & 0 deletions db/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ model ProjectMember {
tags Json?
commentReadStatus CommentReadStatus[]
notes Note[]
formerTeamIds Json?
}

model ProjectPrivilege {
Expand Down
7 changes: 6 additions & 1 deletion src/auth/mutations/resetPassword.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import db from "db"
import { hash256 } from "@blitzjs/auth"
import { SecurePassword } from "@blitzjs/auth/secure-password"

/**
* Integration test – requires live DB
* Run with `npm run test:integration`
*/

beforeEach(async () => {
await db.$reset()
})
Expand All @@ -14,7 +19,7 @@ const mockCtx: any = {
},
}

describe("resetPassword mutation", () => {
describe.skip("resetPassword mutation", () => {
it("works correctly", async () => {
expect(true).toBe(true)

Expand Down
5 changes: 4 additions & 1 deletion src/contributors/components/ContributorForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export function ContributorForm<S extends z.ZodType<any, any>>(props: Contributo
/>
)}
<LabelSelectField
className="select text-primary select-bordered border-primary border-2 w-1/2 mb-4 w-1/2"
className="select text-primary bg-base-300 select-bordered bg-base-300 border-primary border-2 w-1/2 mb-4 w-1/2"
name="privilege"
label="Select Privilege:"
options={MemberPrivilegesOptions}
Expand Down Expand Up @@ -164,6 +164,9 @@ export function ContributorForm<S extends z.ZodType<any, any>>(props: Contributo
/>
</span>
</label>
<p className="text-md italic text-base-content/80 mb-2">
Tags only save after you press enter, comma, or semicolon.
</p>
<ReactTags
tags={tags}
name="tags"
Expand Down
89 changes: 88 additions & 1 deletion src/contributors/mutations/deleteContributor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ export default resolver.pipe(
// Get the userId from the associated users array
const userId = contributorToDelete.users[0]!.id

// Reconstruct possible display names used in notification messages
const user = contributorToDelete.users[0]
const possibleDisplayNames: string[] = []

if (user!.firstName && user!.lastName) {
possibleDisplayNames.push(`${user!.firstName} ${user!.lastName}`)
}

if (user!.username) {
possibleDisplayNames.push(user!.username)
}

const notificationMarkerText = " (former contributor)"
const notificationMarkerHtml =
'<span class="text-base-content/70" data-former-contributor="true"> (former contributor)</span>'

// Check if the project member has any privileges related to the project
const projectPrivilege = await db.projectPrivilege.findFirst({
where: {
Expand Down Expand Up @@ -75,6 +91,8 @@ export default resolver.pipe(
},
})

const formerTeamIds = teamProjectMembers.map((teamMember) => teamMember.id)

// Disconnect the user from each team project member individually
for (const teamMember of teamProjectMembers) {
await db.projectMember.update({
Expand All @@ -87,6 +105,72 @@ export default resolver.pipe(
})
}

// Annotate existing notifications that reference this contributor by name
if (possibleDisplayNames.length > 0) {
console.log(
`[deleteContributor] Annotating notifications for user ${userId} with names:`,
possibleDisplayNames
)

const notifications = await db.notification.findMany({
where: {
projectId: contributorToDelete.projectId,
announcement: false,
AND: [
{
NOT: {
message: {
endsWith: notificationMarkerText,
},
},
},
{
NOT: {
message: {
endsWith: notificationMarkerHtml,
},
},
},
{
OR: possibleDisplayNames.map((name) => ({
message: {
contains: name,
mode: "insensitive",
},
})),
},
],
},
select: {
id: true,
message: true,
},
})

console.log(
`[deleteContributor] Found ${notifications.length} notifications requiring markers`
)

await Promise.all(
notifications.map((n) => {
const trimmed = n.message!.trim()
const containsHtml = /<\/?[a-z][\s\S]*>/i.test(trimmed)
const marker = containsHtml ? notificationMarkerHtml : notificationMarkerText

return db.notification.update({
where: { id: n.id },
data: {
message: `${trimmed}${marker}`,
},
})
})
)
} else {
console.log(
`[deleteContributor] No display names detected for user ${userId}, skipping notification annotations`
)
}

// Disconnect the notifications related to the project
const notificationsToUpdate = await db.notification.findMany({
where: {
Expand Down Expand Up @@ -118,7 +202,10 @@ export default resolver.pipe(
// Mark the project member as deleted
const projectMember = await db.projectMember.update({
where: { id: contributorToDelete.id },
data: { deleted: true },
data: {
deleted: true,
formerTeamIds: formerTeamIds.length > 0 ? formerTeamIds : undefined,
},
})

return projectMember
Expand Down
12 changes: 4 additions & 8 deletions src/core/components/DateFormat.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,10 @@ import DateFormat from "./DateFormat"

test("renders Date format", async () => {
const dStr: string = "2024-02-27 1:45 PM"
const expStr = "February 27, 2024 at 13:45:00"
const ntExpStr = "01:45:00 PM"
const expStr = "February 27, 2024 at 13:45"
const date1: Date = new Date(dStr)

render(<DateFormat date={date1}></DateFormat>)
const dateSpan = screen.getByTestId("dateformat-id")
expect(dateSpan).toBeInTheDocument()
const text = await screen.getByText(expStr)
expect(text).toBeInTheDocument()
expect(await screen.queryByText(ntExpStr)).not.toBeInTheDocument()
render(<DateFormat date={date1} />)

expect(await screen.getByText(expStr)).toBeInTheDocument()
})
29 changes: 19 additions & 10 deletions src/core/components/Filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { Column } from "@tanstack/react-table"
function Filter({ column }: { column: Column<any, unknown> }) {
const { filterVariant } = column.columnDef.meta ?? {}
const isHtml = column.columnDef.meta?.isHtml || false
const selectOptions = column.columnDef.meta?.selectOptions as
| { label: string; value: string }[]
| undefined
const columnFilterValue = column.getFilterValue()
const facetedUniqueValues = column.getFacetedUniqueValues()

Expand Down Expand Up @@ -64,16 +67,22 @@ function Filter({ column }: { column: Column<any, unknown> }) {
className={sharedInputStyles}
>
<option value="">All</option>
{sortedUniqueValues.map((value, index) => (
// dynamically generated select options from faceted values feature
<option
value={value}
key={getUniqueKey(value, index)}
dangerouslySetInnerHTML={isHtml ? { __html: value } : undefined}
>
{!isHtml ? value : undefined}
</option>
))}
{selectOptions
? selectOptions.map((option, index) => (
<option value={option.value} key={getUniqueKey(option.value, index)}>
{option.label}
</option>
))
: sortedUniqueValues.map((value, index) => (
// dynamically generated select options from faceted values feature
<option
value={value}
key={getUniqueKey(value, index)}
dangerouslySetInnerHTML={isHtml ? { __html: value } : undefined}
>
{!isHtml ? value : undefined}
</option>
))}
</select>
) : filterVariant === "multiselect" ? (
<div className="dropdown">
Expand Down
2 changes: 2 additions & 0 deletions src/core/components/GetWidgetDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export function GetTableDisplay({ data, columns, type }) {
return (
<Table
columns={columns}
enableGlobalSearch={false}
data={data}
classNames={{
thead: "text-base text-base-content",
Expand Down Expand Up @@ -85,6 +86,7 @@ export function GetProjectSummaryDisplay({ project, projectManagers }) {
<Table
columns={projectManagersColumns}
data={projectManagers}
enableGlobalSearch={false}
classNames={{
thead: "text-base",
tbody: "text-base",
Expand Down
Loading