Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
2b73ab3
New Playwright Test - can remove member WIP
jskunkle Nov 7, 2024
cdd2de3
Working test
jskunkle Nov 7, 2024
2b69963
remove stray console.log
HerbCaudill Jan 17, 2025
68a6e56
by: simplify types
HerbCaudill Jan 17, 2025
e6f9dd0
projects: no space after colon
HerbCaudill Jan 17, 2025
f67f06b
popover: leave styling (shadow etc) to implementation
HerbCaudill Jan 17, 2025
2b0bbbe
add AutocompleteMenu component
HerbCaudill Jan 17, 2025
69abe6f
TimeEntryInput: add autocomplete
HerbCaudill Jan 17, 2025
39e0831
findAutocompleteQuery.test
HerbCaudill Jan 17, 2025
496b2c7
position autocomplete menu next to cursor
HerbCaudill Jan 22, 2025
7454848
Merge remote-tracking branch 'origin/main' into herb/x-39-autocomplet…
HerbCaudill Jan 22, 2025
f83969c
TimeEntryInput: use onInput
HerbCaudill Jan 22, 2025
d7f0644
update Playwright tests
HerbCaudill Jan 22, 2025
04b88b8
add aria properties to autocomplete
HerbCaudill Jan 22, 2025
42d3160
add tests for autocomplete
HerbCaudill Jan 22, 2025
44b8e40
projects: replace slash with dash
HerbCaudill Jan 22, 2025
71a58c3
Extract AutocompleteTextarea component
HerbCaudill Jan 23, 2025
d740a8b
sort out keystroke handling between autocomplete menu and textarea
HerbCaudill Jan 23, 2025
f61b78d
TimeEntryInput: turn border red if there's an error
HerbCaudill Jan 23, 2025
bbe3c70
AutocompleteTextarea: fix justAutocompleted calculation
HerbCaudill Jan 23, 2025
6e1687f
MyWeek: style tweaks
HerbCaudill Jan 23, 2025
a0a09ca
styling & animation tweaks throughout
HerbCaudill Jan 24, 2025
b7f2f2f
set up year navigation
HerbCaudill Jan 24, 2025
620e477
add year indexes
HerbCaudill Jan 27, 2025
ad98d35
stub out HoursReport
HerbCaudill Jan 27, 2025
d9872ae
optimization: don't recreate the index every time
HerbCaudill Jan 27, 2025
91f7160
generateTimeEntries: more realistic patterns
HerbCaudill Jan 28, 2025
cb0ec26
fix year accessors
HerbCaudill Jan 28, 2025
4ca7ee7
TimeEntryCollection: add contact index
HerbCaudill Jan 28, 2025
6f7af11
move utility file
HerbCaudill Jan 28, 2025
79d8714
HoursReport: wip
HerbCaudill Jan 28, 2025
0063f39
add WeekNav stories
HerbCaudill Jan 28, 2025
4dcd94a
storybook config: remove unused code
HerbCaudill Jan 28, 2025
eb08b7c
yearFromString: don't enforce limits here
HerbCaudill Jan 28, 2025
995cead
add YearNav stories
HerbCaudill Jan 28, 2025
9614b31
YearNav: disable prev and next buttons where appropriate
HerbCaudill Jan 28, 2025
141cd90
MyWeek.stories: rename stories, refactor
HerbCaudill Jan 28, 2025
c1f5823
Add mockdate
HerbCaudill Jan 28, 2025
55666f9
generateTimeEntries: pass in procrastinators
HerbCaudill Jan 28, 2025
39c3b82
add HoursReport stories
HerbCaudill Jan 28, 2025
fbcff4d
TimeEntryReport.test: update snapshots
HerbCaudill Jan 28, 2025
ac699c0
formatDateRange: add options
HerbCaudill Jan 28, 2025
27779b7
TimeEntryView.test: update snapshots
HerbCaudill Jan 28, 2025
a57cf73
Effect: add Array to exports
HerbCaudill Jan 29, 2025
ab7f52a
add rankItems (for shame rankings)
HerbCaudill Jan 29, 2025
f9507e4
avatar: add 2xs size
HerbCaudill Jan 29, 2025
87a5fd5
generateTimeEntries: change range of hours worked in normal day
HerbCaudill Jan 29, 2025
4cb7540
generateTimeEntries: add option to omit contact (e.g. Colleen)
HerbCaudill Jan 29, 2025
8daea31
HoursReport: feature complete
HerbCaudill Jan 29, 2025
04b9cec
make fake extended contacts
HerbCaudill Jan 29, 2025
d33b6f4
DoneEditable: fix import order for linter
HerbCaudill Jan 29, 2025
cb6d6de
hours.$year: limit year to min and max in data
HerbCaudill Jan 29, 2025
568eaa0
avatar: set rounding per size
HerbCaudill Jan 29, 2025
4822b69
HoursReport: layout tweaks
HerbCaudill Jan 29, 2025
e4f0726
refactor some stuff
HerbCaudill Jan 29, 2025
d98f915
TimeEntryGenerator: change 200 week option to 100
HerbCaudill Jan 29, 2025
6f837f9
HoursReport: import order
HerbCaudill Jan 30, 2025
1fbdede
generateTimeEntries.test: adapt to changes in generator
HerbCaudill Jan 30, 2025
9a4e129
remove code folded into HoursReport.tsx
HerbCaudill Jan 30, 2025
ad1ce29
App: fix hoursArea selector
HerbCaudill Jan 30, 2025
8d31f24
rename to rankByScore
HerbCaudill Jan 30, 2025
4315e9a
WeekNav: make consistent with YearNav
HerbCaudill Jan 30, 2025
82f4965
make WeekNav & YearNav consistent
HerbCaudill Jan 30, 2025
100fb0d
move mockdate to devDeps
HerbCaudill Jan 30, 2025
56ca0f5
name page components in routes/** consistently
HerbCaudill Jan 30, 2025
dd52cf4
fix 2 failing playwright tests
HerbCaudill Jan 30, 2025
027f372
Fix last failing playwright test
HerbCaudill Jan 30, 2025
66224ac
App: remove unused method
HerbCaudill Jan 30, 2025
bd96526
Merge branch 'main' into playwright-test-remove-member
HerbCaudill Jan 31, 2025
c96ac41
Merge branch 'herb/x-82-playwright-tests-for-dones-sync-failing' into…
HerbCaudill Jan 31, 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
21 changes: 0 additions & 21 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,3 @@ function getUiDirs(dir: string = path.join(__dirname, "../app")): string[] {
const otherDirs = dirs.filter(dir => dir.name !== "ui")
return uiDirs.concat(otherDirs.flatMap(subDir => getUiDirs(fullPath(subDir)).sort()))
}

function getParent(dir: string) {
return path.basename(path.dirname(dir))
}

function stripPunctuation(str: string) {
return str.replace(/[^\w\s]/g, "")
}

function titleCase(str: string) {
return str
.split(" ")
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ")
}

function getTitlePrefix(dir: string) {
const parent = getParent(dir)
const title = titleCase(stripPunctuation(parent)).replace("App", "")
return `Components/${title}`
}
1 change: 0 additions & 1 deletion app/context/Auth/getSyncServer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const isDev = !import.meta.env.PROD
const devPort = (import.meta.env.VITE_SYNC_SERVER_PORT as string) ?? "3030"
console.log("devPort", devPort)

const httpProtocol = isDev ? "http:" : "https:"
const wsProtocol = isDev ? "ws:" : "wss:"
Expand Down
23 changes: 15 additions & 8 deletions app/context/Database/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,24 @@ export abstract class Collection<
}

/** Adds an item to the collection */
add(item: Item) {
add(arg: Item | Item[]) {
const items = Array.isArray(arg) ? arg : [arg]

return E.gen(this, function* () {
// add the item to all indexes
for (const index of this.allIndexes) yield* index.add(item)
// add each item to all indexes
for (const index of this.allIndexes) {
yield* index.add(items)
}

// update automerge-repo
this.change((root: Types.Mutable<RootEncoded>) => {
const rootElement = root[this.name]
rootElement[item.id] = this.encode(item)
})
return item
for (const item of items) {
this.change((root: Types.Mutable<RootEncoded>) => {
const rootElement = root[this.name]
rootElement[item.id] = this.encode(item)
})
}

return arg
})
}

Expand Down
36 changes: 18 additions & 18 deletions app/context/Database/Indexes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export abstract class BaseIndex<Item extends CollectionItem> {
if (this.unique && existingItems.length > 0)
throw new Error(`Duplicate key '${key}' found while building index`)

return { ...acc, [key]: [...existingItems, item] }
acc[key] = [...existingItems, item]
return acc
}, {})
}

Expand All @@ -58,33 +59,35 @@ export abstract class BaseIndex<Item extends CollectionItem> {
}

/** Adds a new item. For unique indexes, throws an error if an item with the same key already exists. */
add(itemToAdd: Item) {
const key = this.accessor(itemToAdd)
const existingItems = this.index[key] ?? []
add(arg: Item | Item[]) {
const items = Array.isArray(arg) ? arg : [arg]

if (this.unique && existingItems.length > 0)
return E.fail(new KeyExistsError({ field: this.name, value: key }))
for (const item of items) {
const key = this.accessor(item)

this.index[key] ??= []

const existingItems = this.index[key] ?? []

this.index = {
...this.index,
[key]: [...existingItems, itemToAdd],
if (this.unique && existingItems.length > 0)
return E.fail(new KeyExistsError({ field: this.name, value: key }))

this.index[key] = [...existingItems, item]
}

return E.succeed(itemToAdd)
return E.succeed(arg)
}

/** Updates an existing item. Throws an error if there is not an existing item. */
update(item: Item) {
return E.gen(this, function* () {
const key = this.accessor(item)

const items = [yield* this.find(key)].flat() as Item[]
const matchingItem = items.find(i => i.id === item.id)
if (!matchingItem) yield* E.fail(new KeyNotFoundError({ field: this.name, value: key }))

this.index = {
...this.index,
[key]: items.map(i => (i.id === item.id ? item : i)),
}
this.index[key] = items.map(i => (i.id === item.id ? item : i))
})
}

Expand All @@ -96,10 +99,7 @@ export abstract class BaseIndex<Item extends CollectionItem> {
const matchingItem = items.find(i => i.id === item.id)
if (!matchingItem) yield* E.fail(new KeyNotFoundError({ field: this.name, value: key }))

this.index = {
...this.index,
[key]: items.filter(i => i.id !== item.id),
}
this.index[key] = items.filter(i => i.id !== item.id)
})
}

Expand Down
30 changes: 30 additions & 0 deletions app/context/Database/test/Collection.benchmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { LocalDate } from "@js-joda/core"
import { clients } from "data/clients"
import { contacts } from "data/contacts"
import { projects } from "data/projects"
import { $ } from "lib/Effect"
import { generateTimeEntries } from "lib/generateTimeEntries"
import { TimeEntryCollection } from "schema/TimeEntryCollection"
import { bench, describe } from "vitest"

const addEntries = (weekCount: number) => {
const entries = generateTimeEntries({
clients,
contacts,
projects,
startDate: LocalDate.now(),
weekCount,
})

const collection = new TimeEntryCollection([])
$(collection.add(entries))
}

describe("collection.add()", () => {
bench("1 week", async () => addEntries(1))
bench("5 weeks", async () => addEntries(5))
bench("10 weeks", async () => addEntries(10))
// bench("50 weeks", async () => addEntries(50))
})

// describe("collection.all()", () => {})
29 changes: 27 additions & 2 deletions app/context/Database/test/Collection.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { LocalDate } from "@js-joda/core"
import { contacts } from "data/contacts"
import { $, E, Either } from "lib/Effect"
import { generateDones } from "lib/generateDones"
import { Contact } from "schema/Contact"
import { DoneEntry } from "schema/DoneEntry"
import { DoneEntryCollection } from "schema/DoneEntryCollection"
Expand Down Expand Up @@ -84,6 +86,12 @@ describe("Collection", () => {
const items = $(collection.findBy("week", "2024-11-03"))
expect(items).toHaveLength(8)
})

it("finds an item by year", () => {
const { collection } = setup()
const items = $(collection.findBy("year", 2024))
expect(items).toHaveLength(14)
})
})

describe("add", () => {
Expand All @@ -100,6 +108,23 @@ describe("Collection", () => {
expect($(collection.findBy("contactId", alice.id))).toHaveLength(8)
expect($(collection.findBy("week", "2024-11-03"))).toHaveLength(9)
})

it("adds multiple items to the collection", () => {
const { collection } = setup()
expect($(collection.all())).toHaveLength(14)

// generate a bunch of dones
const dones = generateDones({
today: LocalDate.parse("2024-11-09"),
weeks: 1,
productivity: 3,
enthusiasm: 0.1,
contacts,
})
const result = $(collection.add(dones))
expect(result).toBe(dones)
expect($(collection.all())).toHaveLength(224)
})
})

describe("update", () => {
Expand All @@ -126,10 +151,10 @@ describe("Collection", () => {
const { collection } = setup()
const dones = $(collection.all())
const item = dones[0]
$(collection.update({ ...item, likes: [bob.id] }))
$(collection.update({ ...item, likes: [bob] }))

const updated = $(collection.find(item.id))
expect(updated.likes).toEqual([bob.id])
expect(updated.likes).toEqual([bob])
})
})

Expand Down
7 changes: 6 additions & 1 deletion app/data/contacts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ContactId, Contact } from "schema/Contact"
import { type ContactId, Contact, ExtendedContact } from "schema/Contact"

// These will be replaced with contact information that people can edit

Expand Down Expand Up @@ -71,3 +71,8 @@ export const contacts = [
id: contact.userName as ContactId,
}),
)

/** for testing purposes */
export const fakeExtendedContacts = contacts.map(
contact => new ExtendedContact({ ...contact, isSelf: false, isMember: true, isAdmin: true }),
)
116 changes: 58 additions & 58 deletions app/data/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,72 @@ import { Project } from "schema/Project"
import tailwindColors from "tailwindcss/colors"

const projectList = `
Business: Contracts yes Contract negotiation & other back-and-forth
Business: Marketing Work on DevResults.com, writing blogs, creating materials, etc.
Business: Outreach sometimes Conferences, coffee, donuts, cold calls, etc
Business: Proposals yes Writing/editing proposals for specific prospects
DevOps: Azure migration
DevOps: CI configuration
DevOps: General Non-project-oriented DevOps work: server/VM/database configuration, etc.
Feature: Activity-Specific Stuff
Feature: Ag Grid conversions
Feature: API
Feature: Baselines
Feature: ContentObject Validation
Feature: Count First/Last
Feature: Count Unique Per Reporting Period
Feature: Dashboard
Feature: Data Tables
Feature: DevIndicators
Feature: Diagnostics
Feature: Dropbox integration
Feature: EditGrid
Feature: Enterprise
Feature: Frameworks Index 2022-S01 etc
Feature: GDPR
Feature: Google Drive integration
Feature: Ground Truth
Feature: IATI
Feature: Indexify
Feature: Instance Export
Feature: Internal Tooling Includes instance bootstrapper features, DevResults CLI, etc.
Feature: Localization Includes managing LanguageStrings, getting translations, etc.
Feature: Matrix
Feature: Metadata visualization
Feature: Notifications
Feature: Partner Permissions 2022 sprint 8 and associated work
Feature: PowerBI
Feature: Project X
Feature: Pseudonyms
Feature: Public Site
Feature: RDTs
Feature: Self-serve
Feature: Single Sign-On , and supporting clients
Feature: Standard RP Picker
Feature: Survey123
Feature: SurveyCTO
Feature: Too small to name But you can name it in the comments!
Feature: WorldAdminDivisions update
Business:Contracts yes Contract negotiation & other back-and-forth
Business:Marketing Work on DevResults.com, writing blogs, creating materials, etc.
Business:Outreach sometimes Conferences, coffee, donuts, cold calls, etc
Business:Proposals yes Writing/editing proposals for specific prospects
DevOps:Azure migration
DevOps:CI configuration
DevOps:General Non-project-oriented DevOps work:server/VM/database configuration, etc.
Feature:Activity-Specific Stuff
Feature:Ag Grid conversions
Feature:API
Feature:Baselines
Feature:ContentObject Validation
Feature:Count First-Last
Feature:Count Unique Per Reporting Period
Feature:Dashboard
Feature:Data Tables
Feature:DevIndicators
Feature:Diagnostics
Feature:Dropbox integration
Feature:EditGrid
Feature:Enterprise
Feature:Frameworks Index 2022-S01 etc
Feature:GDPR
Feature:Google Drive integration
Feature:Ground Truth
Feature:IATI
Feature:Indexify
Feature:Instance Export
Feature:Internal Tooling Includes instance bootstrapper features, DevResults CLI, etc.
Feature:Localization Includes managing LanguageStrings, getting translations, etc.
Feature:Matrix
Feature:Metadata visualization
Feature:Notifications
Feature:Partner Permissions 2022 sprint 8 and associated work
Feature:PowerBI
Feature:Project X
Feature:Pseudonyms
Feature:Public Site
Feature:RDTs
Feature:Self-serve
Feature:Single Sign-On , and supporting clients
Feature:Standard RP Picker
Feature:Survey123
Feature:SurveyCTO
Feature:Too small to name But you can name it in the comments!
Feature:WorldAdminDivisions update
Out
Overhead General meetings, company process stuff, finance stuff, HR, onboarding, learning…
Security All things security
Support: API mostly Any help provided to helping users access and use the API
Support: External tooling Providing support for "other tools" outside of DevResults (e.g. PowerBI)
Support: Ongoing mostly Includes engineering support to individual clients (but not bug fixing)
Support: Scaling training & help Videos, help materials, etc.
Support: Setup yes Everything before the instance goes live
Support: Training sometimes Training dedicated to one specific client
Tech wealth: Bug fixin Probably mostly on call. Note feature in comments if it's a major feature fix.
Tech wealth: Other Catch-all for general improvements to the codebase
Tech wealth: Optimization Work to improve performance within the app
Tech wealth: TypeScript
Tech wealth: Webpack
Support:API mostly Any help provided to helping users access and use the API
Support:External tooling Providing support for "other tools" outside of DevResults (e.g. PowerBI)
Support:Ongoing mostly Includes engineering support to individual clients (but not bug fixing)
Support:Scaling training & help Videos, help materials, etc.
Support:Setup yes Everything before the instance goes live
Support:Training sometimes Training dedicated to one specific client
Tech wealth:Bug fixin Probably mostly on call. Note feature in comments if it's a major feature fix.
Tech wealth:Other Catch-all for general improvements to the codebase
Tech wealth:Optimization Work to improve performance within the app
Tech wealth:TypeScript
Tech wealth:Webpack
Thought Leadership `
.trim()
.split("\n")
.map(line => {
const [fullCode, requiresClient, description] = line.trim().split("\t")
const [code, subCode] = fullCode.split(/:\s+/).map(s => s.trim().replaceAll(" ", "-"))
const [code, subCode] = fullCode.split(/:\s*/).map(s => s.trim().replaceAll(" ", "-"))
return {
code,
subCode,
Expand Down
4 changes: 3 additions & 1 deletion app/hooks/useRedirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import { useLocation, useNavigate } from "@remix-run/react"
import { useEffect } from "react"

export function useRedirect({ from, to }: Params) {
export function useRedirect({ from, to, condition = true }: Params) {
const { pathname, state } = useLocation()
const navigate = useNavigate()
useEffect(() => {
if (!condition) return
if (typeof from === "string" && pathname === from) {
console.log(`redirecting from ${from} to ${to}`)
navigate(to, { state })
Expand All @@ -21,4 +22,5 @@ export function useRedirect({ from, to }: Params) {
type Params = {
from: string | RegExp
to: string
condition?: boolean
}
Loading
Loading