Skip to content
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

Pane-based layout for the OKR timeline view #841

Merged
merged 148 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
148 commits
Select commit Hold shift + click to select a range
b4d155f
Add pane layout components
petterhj Aug 18, 2023
1620555
Organize new OKR related state in separate store module
petterhj Aug 24, 2023
e5c0101
Add panes for OKR timeline, objectives and key results
petterhj Aug 24, 2023
c51c46d
Use pane layout for item OKR view
petterhj Aug 24, 2023
68efa0b
Add detail widgets to objective and key result panes
petterhj Aug 24, 2023
cea6afb
Remove old OKR detail views and components
petterhj Aug 24, 2023
7e06dfd
Clean up store state for old OKR detail views
petterhj Aug 24, 2023
b32d151
Ensure period is resolved in objective and key result details widget
petterhj Aug 24, 2023
81e9674
Fix incorrect getter name in store
petterhj Aug 25, 2023
7f3bc05
Cleanup and changelog
petterhj Aug 25, 2023
714240c
Encode user-controlled query params
petterhj Aug 28, 2023
b47c7aa
Use Punkt typography helper classes directly
petterhj Aug 29, 2023
3a8a591
Fix changelog typo
petterhj Aug 29, 2023
210f0af
Simplify computed property
petterhj Aug 29, 2023
cf578db
Only show scrollbar when content is clipped
petterhj Sep 1, 2023
058f27c
Add more flexible design for objectives and key result cards
petterhj Sep 1, 2023
82fb6ab
Use absolute positioning for pane close button
petterhj Sep 1, 2023
166b48b
Adjust timeline background and period indicator colors
petterhj Sep 1, 2023
a003052
Adjust pane layout for objective and key result details
petterhj Sep 1, 2023
367aff2
Tighten up the timeline view somewhat
petterhj Sep 1, 2023
16bc05b
Balance titles for objectives and key results
petterhj Sep 1, 2023
dececdd
Fix missing translations
petterhj Sep 4, 2023
8fe0a61
Lower minimum PPD value for the timeline view
petterhj Sep 4, 2023
bb2fa30
Remove unused class name
petterhj Sep 4, 2023
34c867c
Add event bus for pane transition events
petterhj Sep 4, 2023
370ad4f
Scroll to active objective after pane is opened
petterhj Sep 4, 2023
4607646
Scroll to objective when added to workbench
petterhj Sep 5, 2023
dc4ca39
Remove superfluous tooltip
petterhj Sep 6, 2023
b5e62f9
Fix translation typo
petterhj Sep 6, 2023
6778b72
Respect objective period length for cards in the timeline
petterhj Sep 7, 2023
70ff948
Merge pull request #858 from oslokommune/truncated-okr-cards
petterhj Sep 13, 2023
9c16562
Merge remote-tracking branch 'origin/main' into three-pane-okrs
petterhj Sep 25, 2023
2246dc5
Do not scroll to period on mount when an objective is active
petterhj Sep 25, 2023
6a0758b
Load all components synchronously
petterhj Sep 25, 2023
fae89b9
Sync OKR and measurement header title style
petterhj Sep 26, 2023
099e26e
Add breadcrumbs to objective and key result panes
petterhj Sep 26, 2023
3f8243a
Merge pull request #876 from oslokommune/pane-design-adjustments
petterhj Sep 26, 2023
2c438ad
Merge branch 'main' into three-pane-okrs
simenheg Sep 28, 2023
ddcf659
Merge branch 'main' into three-pane-okrs
simenheg Sep 28, 2023
935d236
Support drag & drop for key results
simenheg Sep 26, 2023
e3da8b1
Tweak class placement to fix margin
simenheg Sep 28, 2023
6689102
Merge pull request #875 from oslokommune/drag-n-drop-key-results
simenheg Sep 28, 2023
c52f7fd
Add checkboxes for toggling workbench objectives
petterhj Sep 28, 2023
82176a0
Remove unused store action mappings
petterhj Sep 28, 2023
7de34da
Fix adding newly created objective to workbench
petterhj Sep 28, 2023
285a14c
Reduce flicker while toggling panes
petterhj Oct 2, 2023
73d55e0
Merge pull request #877 from oslokommune/checkbox-workbench
petterhj Oct 3, 2023
ea1585b
Display contributor tags on objective cards
simenheg Sep 4, 2023
f3ee037
Rename `OkrLinkCard` -> `ObjectiveLinkCard`
simenheg Sep 28, 2023
1ab0256
Rename `okr-link-card` -> `objective-link-card`
simenheg Sep 28, 2023
9aca7a1
Revamp owner/contributor display in objective pane
simenheg Sep 28, 2023
4c0c0eb
Adapt styling to the new checkboxes
simenheg Oct 3, 2023
76d5043
Call `navigate` directly
simenheg Oct 3, 2023
47d36d8
Merge pull request #854 from oslokommune/contributor-tags
simenheg Oct 3, 2023
edf0601
Add contributors to objective
annemerete Oct 5, 2023
57dd82f
Testing without `$bind`
simenheg Oct 5, 2023
7839e20
Color code objectives and key results
petterhj Oct 4, 2023
3b3270c
Merge pull request #878 from oslokommune/okr-color-coding
petterhj Oct 5, 2023
431810d
Show correct objective owner
petterhj Oct 5, 2023
1213679
Merge pull request #880 from oslokommune/fix-owner-tag
petterhj Oct 6, 2023
60b360c
Tweak contributor color pool
simenheg Oct 6, 2023
6e058bb
Merge pull request #881 from oslokommune/color-tweak
simenheg Oct 6, 2023
0257809
Merge branch 'main' into three-pane-okrs
simenheg Oct 9, 2023
a0e39ad
Prevent wrapping in objective card header
petterhj Oct 5, 2023
77bda1e
Place objective period above progress bar
petterhj Oct 5, 2023
1e0efd1
Redesign the key result history listing
petterhj Oct 6, 2023
c1ec159
Merge pull request #882 from oslokommune/key-result-history-list
petterhj Oct 9, 2023
455ad43
Move progress graph to key result card
petterhj Oct 9, 2023
e1904cd
Merge pull request #884 from oslokommune/graph-in-kr-card
petterhj Oct 10, 2023
0ad389a
Merge branch 'main' into three-pane-okrs
simenheg Oct 10, 2023
40db0d5
Fix key result values table responsiveness
petterhj Oct 10, 2023
ab9ebda
Merge branch 'main' into three-pane-okrs
simenheg Oct 10, 2023
dede2fa
Preserve line breaks
petterhj Oct 10, 2023
1cc89c4
Add header label for edited by column
petterhj Oct 10, 2023
87cc8e4
Merge pull request #886 from oslokommune/fix-kr-value-table
petterhj Oct 10, 2023
7755f72
Added async await logic
annemerete Oct 12, 2023
b08daf4
Avoid duplicate objective listing
simenheg Oct 12, 2023
6f5c854
Linting
annemerete Oct 12, 2023
bb4ac24
Merge pull request #891 from oslokommune/fix-duplicate-objectives
simenheg Oct 12, 2023
80c1988
Merge branch 'main' into three-pane-okrs
simenheg Oct 12, 2023
54a541f
Merge branch 'main' into three-pane-okrs
simenheg Oct 12, 2023
9645877
Hide archived external objectives
simenheg Oct 12, 2023
6926ff5
Merge pull request #892 from oslokommune/hide-archived-externals
simenheg Oct 13, 2023
a964f05
Fix after code review
annemerete Oct 13, 2023
01d4de9
Merge pull request #890 from oslokommune/add_contributors_to_objective
simenheg Oct 17, 2023
26a9e31
Merge branch 'main' into three-pane-okrs
simenheg Oct 17, 2023
575c0ad
Use less flamboyant color coding of objectives and key results
petterhj Oct 18, 2023
df9cbc7
Merge pull request #896 from oslokommune/pane-de-saturation
petterhj Oct 18, 2023
9dc84b8
Streamline the key result values list
petterhj Oct 18, 2023
45c23e7
Make tweaks to the drag and drop styling
simenheg Oct 9, 2023
20cbc14
Merge pull request #887 from oslokommune/drag-n-drop-tweaks
simenheg Oct 18, 2023
683dfe3
Merge pull request #897 from oslokommune/kr-history-take-2
petterhj Oct 18, 2023
385191c
Merge branch 'main' into three-pane-okrs
simenheg Oct 19, 2023
b22da57
Merge branch 'main' into three-pane-okrs
simenheg Oct 19, 2023
0f88604
Add min/max indicator to progress bars
petterhj Oct 19, 2023
bb2fa18
Allow secondary value indicator for progress bars
petterhj Oct 19, 2023
254ada2
Clean out disused progress util and translation keys
petterhj Oct 20, 2023
201fec0
Prevent overlapping indicators
petterhj Oct 20, 2023
521cda2
Minor sizing adjustments
petterhj Oct 20, 2023
d1b31be
Merge pull request #900 from oslokommune/progressbar-indicators
petterhj Oct 20, 2023
b808d1e
Merge branch 'main' into three-pane-okrs
simenheg Oct 20, 2023
ed50285
Simplify objective details card
petterhj Oct 20, 2023
f8ef29d
Merge pull request #902 from oslokommune/objective-details
petterhj Oct 20, 2023
2357a92
Merge branch 'main' into three-pane-okrs
simenheg Oct 25, 2023
9193f36
Make org admins able to add objective contributors
simenheg Nov 3, 2023
109e21e
Merge pull request #907 from oslokommune/admin-create-contributors-bu…
simenheg Nov 6, 2023
2333533
Merge branch 'main' into three-pane-okrs
simenheg Nov 6, 2023
cfd3ccb
Clearly indicate objective inheritance and contributors
petterhj Nov 3, 2023
f3eaa2a
Fix item tag text wrapping
petterhj Nov 6, 2023
d98c2f2
Reduce number of similar colors
petterhj Nov 6, 2023
abd54b5
Styling cleanup
petterhj Nov 6, 2023
c51eafc
Merge pull request #908 from oslokommune/coop-tags
petterhj Nov 6, 2023
29870c0
Fix key result link card `owner` argument type
simenheg Nov 7, 2023
fe05bfa
Persist timeline PPD using local storage
petterhj Nov 6, 2023
7ecdf11
Merge pull request #910 from oslokommune/persist-timeline-ppd
petterhj Nov 7, 2023
b9e4c0c
Merge pull request #911 from oslokommune/fix-owner-arg
simenheg Nov 7, 2023
f007726
Merge branch 'main' into three-pane-okrs
simenheg Nov 7, 2023
0814136
Support objective lifting
simenheg Oct 13, 2023
1cb8137
Forbid lifting when there are foreign contributors
simenheg Nov 1, 2023
a42e025
Unfocus objective after lifting it
simenheg Nov 1, 2023
dbd5d20
Tweak lift warning texts
simenheg Nov 1, 2023
835cfa2
Fix Firestore rule for objective updates
simenheg Nov 1, 2023
df6e7b8
Only close pane when the objective disappears
simenheg Nov 1, 2023
481f3d9
Link to newly lifted objectives
simenheg Nov 1, 2023
ede1d55
Add missing translation
simenheg Nov 1, 2023
251fa0e
Only objective owners cam edit objectives
simenheg Nov 7, 2023
08f2fb8
Only key result owners can edit key results
simenheg Nov 7, 2023
707338b
Replace route when lifting objective away
simenheg Nov 7, 2023
519813e
Merge pull request #894 from oslokommune/objective-lift
simenheg Nov 7, 2023
2404a70
Filter ownerOptions on keyResult
annemerete Nov 2, 2023
0831846
Filter ownerOptions on keyResult
annemerete Nov 7, 2023
79113f6
Merge pull request #905 from oslokommune/key_result_edit_rights
annemerete Nov 7, 2023
e7987f8
Merge branch 'main' into three-pane-okrs
simenheg Nov 8, 2023
6ccc517
Fixed computed syntax
annemerete Nov 8, 2023
b9f092d
Use Punkt tag in objective card title
petterhj Nov 8, 2023
d605457
Add item name to OKR and KPI pane titles
petterhj Nov 8, 2023
7aa2fd4
Adjust objective owner label wording
petterhj Nov 8, 2023
4a7fb95
Merge pull request #917 from oslokommune/more-tress-adjustments
petterhj Nov 9, 2023
030938a
Fix CodeQL warning about useless conditional
simenheg Nov 8, 2023
38a1b0a
Merge pull request #916 from oslokommune/fix-codeql-warning
simenheg Nov 10, 2023
944e019
Fix sub KPI bindings
petterhj Nov 8, 2023
e74a941
Merge pull request #919 from oslokommune/fix-sub-kpis
petterhj Nov 10, 2023
2cd4092
More objective contributors Firestore rule tweaks
simenheg Nov 8, 2023
ce42360
Fix typo
simenheg Nov 10, 2023
ec10217
Merge pull request #918 from oslokommune/oc-firestore-rule-tweaks
simenheg Nov 10, 2023
3eb32ea
Fix scroll to objective in Chrome
petterhj Nov 10, 2023
1c739ef
Do not select an objective by default when adding period to workbench
petterhj Nov 10, 2023
d3197dd
Merge pull request #921 from oslokommune/timeline-interaction-fixes
petterhj Nov 13, 2023
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ All notable changes to this project will be documented in this file. The format

### Changed

- Detail views for both objectives and key results are now shown as panes in the
OKR timeline view. The number of simultaneously visible panes depends on the
viewport size (and is otherwise stacked). Clicking objectives in the timeline
now toggles the detail pane rather than adding objectives to a list. To group
objectives in a list (and see combined progression), the meta key must now be
pressed while selecting one or more objectives.
- Key results can now be rearranged by drag and drop.
- New key results are now given a start value of 0, a target value of 100, and
percentage as unit of measurement by default.
- Objectives can now be "lifted" from a product or department to the level above
(to a department or organization, respectively).
- The API authorization mechanism has been reworked. The API now accepts a pair
of `okr-client-id` and `okr-client-secret` headers to authorize clients.
The interface for managing client credentials can be found in the item
Expand All @@ -23,6 +32,8 @@ All notable changes to this project will be documented in this file. The format

- Fixed Markdown rendering of descriptions on objective, key result, and
measurement detail pages.
- Product result indicators and key figures are now correctly included as part
of parent measurements when switching between items.

### Security

Expand Down
41 changes: 41 additions & 0 deletions firestore.indexes.json
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,24 @@
}
]
},
{
"collectionGroup": "objectiveContributors",
"queryScope": "COLLECTION",
"fields": [
{
"fieldPath": "archived",
"order": "ASCENDING"
},
{
"fieldPath": "item",
"order": "ASCENDING"
},
{
"fieldPath": "objective",
"order": "ASCENDING"
}
]
},
{
"collectionGroup": "secrets",
"queryScope": "COLLECTION",
Expand Down Expand Up @@ -501,6 +519,29 @@
"queryScope": "COLLECTION_GROUP"
}
]
},
{
"collectionGroup": "objectiveContributors",
"fieldPath": "auto",
"ttl": false,
"indexes": [
{
"order": "ASCENDING",
"queryScope": "COLLECTION"
},
{
"order": "DESCENDING",
"queryScope": "COLLECTION"
},
{
"arrayConfig": "CONTAINS",
"queryScope": "COLLECTION"
},
{
"arrayConfig": "CONTAINS",
"queryScope": "COLLECTION_GROUP"
}
]
}
]
}
155 changes: 139 additions & 16 deletions firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,96 @@ service cloud.firestore {
return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfParent);
}

//
// Return true if the current user is an admin of the organization that the
// document's `item` belongs to *after* performing the action.
//
function isAdminOfItemAfter(document, type) {
let userDoc = getUserDoc();
let userIsAdmin = isAdmin();
let item = getAfter(/databases/$(database)/documents/$(type)/$(document));
let itemDoc = getAfter(item.data.item);

// Check whether the item of the document the user tries to access has
// an organization – this means that it is a product or department.
let hasOrgDocumentInItem = 'organization' in itemDoc.data;
let isAdminFromOrgOfProdOrDep = userIsAdmin && hasOrgDocumentInItem && getAfter(itemDoc.data.organization).id in userDoc.data.admin;

// Check whether the user has access to the item, which is an
// organization (given that the first check is false).
let isAdminOfItem = userIsAdmin && !isAdminFromOrgOfProdOrDep && itemDoc.id in userDoc.data.admin;

return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfItem);
}

//
// Return true if the current user is an admin of the organization that the
// document's `item` belongs to *before* performing the action.
//
function isAdminOfItemBefore(document, type) {
let userDoc = getUserDoc();
let userIsAdmin = isAdmin();
let item = get(/databases/$(database)/documents/$(type)/$(document));
let itemDoc = get(item.data.item);

// Check whether the item of the document the user tries to access has
// an organization – this means that it is a product or department.
let hasOrgDocumentInItem = 'organization' in itemDoc.data;
let isAdminFromOrgOfProdOrDep = userIsAdmin && hasOrgDocumentInItem && get(itemDoc.data.organization).id in userDoc.data.admin;

// Check whether the user has access to the item, which is an
// organization (given that the first check is false).
let isAdminOfItem = userIsAdmin && !isAdminFromOrgOfProdOrDep && itemDoc.id in userDoc.data.admin;

return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfItem);
}

//
// Return true if the current user is an admin of the parent of the
// document's objective.
//
function isAdminOfObjectiveParent(document, type) {
let userDoc = getUserDoc();
let userIsAdmin = isAdmin();
let doc = getAfter(/databases/$(database)/documents/$(type)/$(document));
let objectiveDoc = getAfter(doc.data.objective);
let parentDoc = getAfter(objectiveDoc.data.parent);

// Check whether the parent of the document the user tries to access has
// an organization – this means that it is a product or department.
let hasOrgDocumentInParent = 'organization' in parentDoc.data;
let isAdminFromOrgOfProdOrDep = userIsAdmin && hasOrgDocumentInParent && getAfter(parentDoc.data.organization).id in userDoc.data.admin;

// Check whether the user has access to the parent, which is an
// organization (given that the first check is false).
let isAdminOfParent = userIsAdmin && !isAdminFromOrgOfProdOrDep && parentDoc.id in userDoc.data.admin;

return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfParent);
}

//
// Return true if the current user is an admin of the parent of the
// document's objective *before* performing the action.
//
function isAdminOfObjectiveParentBefore(document, type) {
let userDoc = getUserDoc();
let userIsAdmin = isAdmin();
let doc = get(/databases/$(database)/documents/$(type)/$(document));
let objectiveDoc = get(doc.data.objective);
let parentDoc = get(objectiveDoc.data.parent);

// Check whether the parent of the document the user tries to access has
// an organization – this means that it is a product or department.
let hasOrgDocumentInParent = 'organization' in parentDoc.data;
let isAdminFromOrgOfProdOrDep = userIsAdmin && hasOrgDocumentInParent && get(parentDoc.data.organization).id in userDoc.data.admin;

// Check whether the user has access to the parent, which is an
// organization (given that the first check is false).
let isAdminOfParent = userIsAdmin && !isAdminFromOrgOfProdOrDep && parentDoc.id in userDoc.data.admin;

return isSignedIn() && (isAdminFromOrgOfProdOrDep || isAdminOfParent);
}

// Is user signed in
function isSignedIn() {
// TODO: Must be a verified (whitelisted) user
Expand All @@ -90,29 +180,63 @@ service cloud.firestore {
}

//
// Return true if the current user is a member of the parent of the
// document's objective *before* performing the action.
// Return true if the current user is a member of `document.parent`
// *before* performing the action.
//
function isMemberOfObjectiveParentBefore(document, type) {
function isMemberOfParentBefore(document, type) {
let userRef = /databases/$(database)/documents/users/$(request.auth.token.email);
let doc = get(/databases/$(database)/documents/$(type)/$(document));
let objectiveDoc = get(doc.data.objective);
let parentDoc = get(objectiveDoc.data.parent);
let parentDoc = get(doc.data.parent);
let userIsMemberOfParent = userRef in parentDoc.data.team;
return userIsMemberOfParent;
}

//
// Return true if the current user is a member of the parent of the
// document's objective *after* performing the action.
// Return true if the `objective`'s parent is either `item` or its parent
// *after* performing the action.
//
function isMemberOfObjectiveParentAfter(document, type) {
let userRef = /databases/$(database)/documents/users/$(request.auth.token.email);
function objectiveParentIsItemOrItemParent(document, type) {
let doc = getAfter(/databases/$(database)/documents/$(type)/$(document));

let itemDoc = getAfter(doc.data.item);
let objectiveDoc = getAfter(doc.data.objective);
let parentDoc = getAfter(objectiveDoc.data.parent);
let userIsMemberOfParent = userRef in parentDoc.data.team;
return userIsMemberOfParent;
let objectiveParentDoc = getAfter(objectiveDoc.data.parent);

let isProduct = 'department' in itemDoc.data;
let isDepartment = 'organization' in itemDoc.data;

let objectiveParentIsItemParent = (isProduct && getAfter(itemDoc.data.department) == objectiveParentDoc)
|| (!isProduct && isDepartment && getAfter(itemDoc.data.organization) == objectiveParentDoc);

return objectiveParentDoc == itemDoc || objectiveParentIsItemParent;
}

//
// Return true if the current user is a member of `item` *after* performing
// the action.
//
function isMemberOfItemAfter(document, type) {
let userRef = /databases/$(database)/documents/users/$(request.auth.token.email);
let doc = getAfter(/databases/$(database)/documents/$(type)/$(document));

let itemDoc = getAfter(doc.data.item);
let isMemberOfItem = userRef in itemDoc.data.team;

return isMemberOfItem;
}

//
// Return true if the current user is a member of `item` *before*
// performing the action.
//
function isMemberOfItemBefore(document, type) {
let userRef = /databases/$(database)/documents/users/$(request.auth.token.email);
let doc = get(/databases/$(database)/documents/$(type)/$(document));

let itemDoc = get(doc.data.item);
let isMemberOfItem = userRef in itemDoc.data.team;

return isMemberOfItem;
}

function isSelf(document) {
Expand Down Expand Up @@ -180,15 +304,14 @@ service cloud.firestore {
match /objectives/{document} {
allow read: if isSignedIn();
allow create: if isSuperAdmin() || isMemberOfParent(document, 'objectives') || isAdminOfParent(document, 'objectives');
allow update: if isSuperAdmin() || isMemberOfParent(document, 'objectives') || isAdminOfParent(document, 'objectives');
allow update: if isSuperAdmin() || isMemberOfParentBefore(document, 'objectives') || isAdminOfParent(document, 'objectives');
allow delete: if isSuperAdmin();
}

// TODO: Should also allow create/delete by organization admins.
match /objectiveContributors/{document} {
allow read: if isSignedIn();
allow create: if isSuperAdmin() || isMemberOfObjectiveParentAfter(document, 'objectiveContributors');
allow delete: if isSuperAdmin() || isMemberOfObjectiveParentBefore(document, 'objectiveContributors');
allow create: if isSuperAdmin() || (isMemberOfItemAfter(document, 'objectiveContributors') && objectiveParentIsItemOrItemParent(document, 'objectiveContributors')) || (isAdminOfItemAfter(document, 'objectiveContributors') && isAdminOfObjectiveParent(document, 'objectiveContributors'));
allow delete: if isSuperAdmin() || isMemberOfItemBefore(document, 'objectiveContributors') || (isAdminOfItemBefore(document, 'objectiveContributors') && isAdminOfObjectiveParentBefore(document, 'objectiveContributors'));
}

match /periods/{document} {
Expand Down
Loading