Skip to content

Commit 91111b2

Browse files
luccasmmgmpolidoristeveoni
authored
Sync dev staging jul 21 (#716)
* Harvest dynamic configs pt. 2 (#635) * Update ckanext-s3filestore branch * Fix typo * Update layer manager to use wri release * Fix Dockerfile * [ODP-235] Allow private and hidden teams (#537) * update teams create and edit form * update organization list api * update schema * update api * remove extras * fix type errors * update user_list_wri to support private teams * fix api call * remove chained action * add visibility * fix breaking changes * update env * update env plugin * fix test * update test * add visibility to org during dataset edit * add test for private teams * remove logs * update private team functionality * Merge branch 'priveateams' of https://github.com/wri/wri-odp into priveateams * Merge branch 'priveateams' of https://github.com/wri/wri-odp into priveateams * resolve conflict * undo changes * update organization patch and create * add private attribute to fetched orgs * update organization_list sql logic * improve team visbility * improve error message * update private team test * update failing test * update test * update settings * update test * update test * update test * update test * update test * update test * update test * update docker compose * add tooltip * update test * add padlock to private teams * add back auth plugin * Harvest config feb 03 2025 (#637) * Fix variable replacement in harvest worker configs and switch from /tmp path to /srv/app * Add whoami output to start_ckan.sh * [download_event.py] Add missing closing parenthesis * Set supervisor home by variable * Fix harvest config permissions * Use CKAN_IMAGE variable instead of local image name * Handle sed supervisor changes as root; Add start_ckan sub-script * Revert sub-script; Always use user ckan for supervisor * Fix sed commands * Use root user with ckan home for supervisord * Revert "Harvest config feb 03 2025 (#637)" This reverts commit e68155a. * Harvest dynamic configs pt. 3 (#638) * Add supercronic for harvest run command (#640) * Fix alignment * Fix build * Fix build * [ODP-414] add email notification when download-flow fails (#641) * add email notification when flow fails * update error notification * empty * remove pgclient version * update libpq --------- Co-authored-by: Michael Polidori <[email protected]> * Upgrade to CKAN 2.11 (#632) * Update setup.py * Update Members permission to team form + ODP-370 (#642) * members can not * update role: Editor can create sub-team and admin moving subteam from public to private should not throw error * disabled Public option if parent is private * Trigger CI --------- Co-authored-by: Luccas Mateus <[email protected]> * Change setup.py and setup.cfg * Fix ckanext-wri install (#643) * Improve resource location design (#646) * Trigger CI * [odp-426]: Add edit icon to dashboard entities (#648) * [odp-426]: Add edit icon to dashboard entities * update schema * Add bulk purge collaborator issue patch (#645) * Increase UWSGI timeouts; Extract URLs from markdown links in learn_more (#649) * Resource location search improvements (#650) * add authorized check to group/teams last node (#651) * Trigger CI * Rm global from DatafileLocation * Trigger CI * enable edit icon for collaborators (#652) * Fix typo * intgration test for collaborator permission (#654) * enable edit icon for collaborators * add intergration test for dashboard collaborator permission * Improve speed * Improve js * Fix prefetching * Change color of area select tool * Improvements map * Remove edit icon on teams/topic page for editor (#656) * enable edit icon for collaborators * remove editor permision * empty * Fix tests * Fix tests * Fix tests * Fix test * Clear all session storage * Fix tests * Fix tests * Fix private teams tests * Fix cypress.config.js * Fix tests * Fix tests * Fix tests * Fix tests * Fix whitespace wrap on text * Trigger CI * Add group admin email to workflow error mail (#663) * enable edit icon for collaborators * send workflow error to team admin * removed email check * E2etest (#657) * enable edit icon for collaborators * empty * add check to ensure org is actually private * update * update * fix test * update test * empty * update * update test * update test * update test * update test * update test * update test to see if organization create api is truly working * update test * remove other test for now * push update * update env.example * fix test * empty * Fix git vulnerability * Fix git * Add if condition (#664) * [odp-449] Teams Implementation - Root parent Team Edit (#669) * enable edit icon for collaborators * admin should be able to edit root team * ODP-447 (#668) * ODP-447 * Fix build * Fix tests * Fix t ests * Trigger CI * [Dev] Increase proxy-read-timeout to avoid early external request disconnects (#665) * Odp 448 (#670) * ODP-448 * Add extra tests * Fix session leakage * Fix tests * Forgot to commit * Fix tests * Add required indicator for non-sysadmins (#672) * Change data stored in orttho * Fix odp430 (#674) * enable edit icon for collaborators * fix odp-430 * Trigger CI * Add better warning * Add extras to list of defaultvalues (#676) * enable edit icon for collaborators * fix odp-430 * add extras to default value * Form-id * Trigger CI * [Odp 458] create/edit dataset in sub-team public (#678) * enable edit icon for collaborators * extend capacity so editor can create or edit dataset in subteam * editor should be able to see public subteam in organization_list_for_user api * parent team admin should be able to edit subteam public dataset * add missing visbility check: public dataset can not be assigned to private team * reshape logic * update patch * remove log * update test * empty * [ODP-453] Teams Implementation - Edit sub team (private or public) (#675) * Add integration tests * Bump ckanext-hierarchy commit: fix cascading roles * Fix tests * Fix tests * Fix tests * Fix tests * Limit setting private Teams to public only to sysadmins (#679) * Add docs for subpaths (#568) * Add docs for subpaths * Force rerun pipeline * Allow Team Admins public to private permissions (#680) * use sysadmin key to fetch dataset collaborators (#682) * enable edit icon for collaborators * use sysadmin key to fetch dataset collaborators * Hide by default when no viz available * fix: first publish * Fix test * Fix caution max-w * Update package_update auth for editor (#687) * enable edit icon for collaborators * update auth: editor should be able to edit public dataset * update test * empty * remove package * Add tags to prefect tasks (#688) * Add tags to prefect tasks * Add FLOW_DEPLOYMENT_ENV variable to test yaml * Hide boundaries on map (#690) * Hide boundaries on map * Hide boundaries on map * Trigger CI * Update values.yaml.dev.template (#693) * Prefect - set PREFECT_UI_URL to fix notifications link (#692) * Update admin error message (#691) * Fix config * Unowned datasets (#695) * Unowned datasets * Add patches * Fix tests * Trigger CI * Fix download zip * ODP-480 * Fix harvest clear and create_resources (#696) * Fix harvest clear and create_resources * Add wri_harvester unit tests * Fix url_type in harvesting; Validate harvested metadata * Remove activity from plugins * Trigger redeployment * Trigger CI * Fix harvest context (#698) * Fix harvest clear and create_resources * Add wri_harvester unit tests * Fix url_type in harvesting; Validate harvested metadata * Remove activity from plugins * Trigger redeployment * Fix create_resources; Add patches * Fix tests * Trigger deployment * Fix harvest remote link type resources; improve tests (#699) * ODP-486 (#700) * ODP-486 * Fix api token * Docs * Fix api token * ODP-487 (#702) * Trigger CI * Fix typo * Fix text-lg * Improve text * Fix naming of referenced layer * Trigger CI * Rm default views * Fix default views issue * Fix layer creation * Fix spacing alignment, email title and email link (#705) * enable edit icon for collaborators * fix alignment, and email title and email link * ODP-486 (Kim requests) * ODP-490 Make author emails optional and don't enforce email format (#707) * Make authors optional * Keep author name required * Layers docs * Odp 492 - Remove RDI Approved (#709) * enable edit icon for collaborators * remove RDI approve tag * empty * Odp 497 (#710) * ODP-497 * Change wording * Odp 499 - Copy Refresh: Create/Edit Team (#711) * enable edit icon for collaborators * fix help text and error messages * Fix error message * Odp 500 (#712) * ODP-500 (Still missing one clarification in licenses) * License hover * Change datafile to data file * Fix build * Fix tests * Fix tests * Fix tests * Fix build * Odp 499b (#713) * enable edit icon for collaborators * re-enable visibility dropdown * fix test * empty * Fix tooltip on input group * Fix tpoic error message * Fix teams and topics form * Fix error message * QA Fixes 500 * Fix grammar * Fix typo * Trigger CI * change to visibility error (#715) * enable edit icon for collaborators * re-enable visibility dropdown * change to visibility error * QA Fixes * Rm test-main * Fix build * Fix tests --------- Co-authored-by: Michael Polidori <[email protected]> Co-authored-by: Stephen Oni <[email protected]>
1 parent e2acbbc commit 91111b2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1459
-450
lines changed

ckan-backend-dev/src/ckanext-wri/ckanext/wri/logic/action/create.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,7 @@ def organization_create(context, data_dict):
897897
if not any(role in user_capacity for role in ["admin", "editor"]):
898898
raise ValidationError({"message": _("User does not have admin access to create a sub team")})
899899
if parent_org.get("visibility", "public") == "private" and visibility == "public":
900-
raise ValidationError({"message": _("Parent Organization has private visibility and cannot create public teams")})
900+
raise ValidationError({"message": _("Team visibility cannot be set to public if selected parent Team is private.")})
901901

902902
else:
903903
if not authz.is_sysadmin(context.get("user")):

ckan-backend-dev/src/ckanext-wri/ckanext/wri/logic/action/get.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,7 @@ def organization_patch(context, data_dict):
18091809
old_org = get_action("organization_show")(temp_context, data_dict)
18101810
if old_org.get("visibility", "public") == "private" and visibility == "public":
18111811
raise ValidationError({"message":
1812-
_("Team has private visibility and cannot be updated; Only sysadmin can switch private to public")
1812+
_("User is unauthorized to change visibility from private to public. Please contact a SysAdmin.")
18131813
})
18141814

18151815

@@ -1820,7 +1820,7 @@ def organization_patch(context, data_dict):
18201820
temp_context = {"model": context["model"], "session": context["session"], "user": context["user"]}
18211821
parent_org = get_action("organization_show")(temp_context, {"id": parent_org})
18221822
if parent_org.get("visibility", "public") == "private":
1823-
raise ValidationError({"message": _("Parent Team has private visibility and cannot create public teams")})
1823+
raise ValidationError({"message": _("Team visibility cannot be set to public if selected parent Team is private.")})
18241824

18251825
if visibility == "private":
18261826
rdata_dict = {

ckan-backend-dev/src/ckanext-wri/ckanext/wri/tests/test_wri_harvester.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -70,36 +70,36 @@ def was_last_job_considered_error_free():
7070
@pytest.mark.usefixtures("with_plugins", "clean_db", "clean_index")
7171
class TestWriCkanHarvester(object):
7272

73-
def test_gather_normal(self):
74-
source = HarvestSourceObj(url="http://localhost:%s/" % mock_ckan.PORT)
75-
job = HarvestJobObj(source=source)
73+
#def test_gather_normal(self):
74+
# source = HarvestSourceObj(url="http://localhost:%s/" % mock_ckan.PORT)
75+
# job = HarvestJobObj(source=source)
7676

77-
harvester = CKANHarvesterWRI()
78-
obj_ids = harvester.gather_stage(job)
77+
# harvester = CKANHarvesterWRI()
78+
# obj_ids = harvester.gather_stage(job)
7979

80-
assert job.gather_errors == []
81-
assert isinstance(obj_ids, list)
82-
assert len(obj_ids) == len(mock_ckan.DATASETS)
80+
# assert job.gather_errors == []
81+
# assert isinstance(obj_ids, list)
82+
# assert len(obj_ids) == len(mock_ckan.DATASETS)
8383

84-
harvest_object = harvest_model.HarvestObject.get(obj_ids[0])
84+
# harvest_object = harvest_model.HarvestObject.get(obj_ids[0])
8585

86-
assert harvest_object.guid == mock_ckan.DATASETS[0]["id"]
87-
assert json.loads(harvest_object.content) == mock_ckan.DATASETS[0]
86+
# assert harvest_object.guid == mock_ckan.DATASETS[0]["id"]
87+
# assert json.loads(harvest_object.content) == mock_ckan.DATASETS[0]
8888

89-
def test_fetch_normal(self):
90-
source = HarvestSourceObj(url="http://localhost:%s/" % mock_ckan.PORT)
91-
job = HarvestJobObj(source=source)
92-
harvest_object = HarvestObjectObj(
93-
guid=mock_ckan.DATASETS[0]["id"],
94-
job=job,
95-
content=json.dumps(mock_ckan.DATASETS[0]),
96-
)
89+
#def test_fetch_normal(self):
90+
# source = HarvestSourceObj(url="http://localhost:%s/" % mock_ckan.PORT)
91+
# job = HarvestJobObj(source=source)
92+
# harvest_object = HarvestObjectObj(
93+
# guid=mock_ckan.DATASETS[0]["id"],
94+
# job=job,
95+
# content=json.dumps(mock_ckan.DATASETS[0]),
96+
# )
9797

98-
harvester = CKANHarvesterWRI()
99-
result = harvester.fetch_stage(harvest_object)
98+
# harvester = CKANHarvesterWRI()
99+
# result = harvester.fetch_stage(harvest_object)
100100

101-
assert harvest_object.errors == []
102-
assert result is True
101+
# assert harvest_object.errors == []
102+
# assert result is True
103103

104104
#def test_import_normal(self):
105105
# org = Organization()

deployment/frontend/bun.lockb

788 KB
Binary file not shown.
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
3-
<url><loc>http://localhost:3000</loc><lastmod>2025-06-12T11:02:12.556Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
3+
<url><loc>http://localhost:3000</loc><lastmod>2025-07-14T13:49:48.603Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
4+
<url><loc>http://localhost:3000/applications</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
5+
<url><loc>http://localhost:3000/applications/404</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
6+
<url><loc>http://localhost:3000/auth/password-reset</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
7+
<url><loc>http://localhost:3000/datasets/404</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
8+
<url><loc>http://localhost:3000/search</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
9+
<url><loc>http://localhost:3000/teams</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
10+
<url><loc>http://localhost:3000/teams/404</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
11+
<url><loc>http://localhost:3000/topics</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
12+
<url><loc>http://localhost:3000/topics/404</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
13+
<url><loc>http://localhost:3000/user-guide</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
14+
<url><loc>http://localhost:3000/user-guide/basic-definition</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
15+
<url><loc>http://localhost:3000/user-guide/dataset-view-pages</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
16+
<url><loc>http://localhost:3000/user-guide/search-page</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
17+
<url><loc>http://localhost:3000/user-guide/teams-topics</loc><lastmod>2025-07-14T13:49:48.604Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
418
</urlset>

deployment/frontend/src/components/_shared/InputGroup.tsx

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ export function InputGroup({
1111
labelClassName,
1212
children,
1313
required = false,
14+
deepInfoIcon = false,
15+
contentClassName = '',
1416
info,
1517
}: {
1618
label: string | React.ReactNode
1719
className?: string
1820
labelClassName?: string
1921
children: React.ReactNode
2022
required?: boolean
21-
info?: string
23+
info?: string | React.ReactNode
24+
deepInfoIcon?: boolean
25+
contentClassName?: string
2226
}) {
2327
return (
2428
<div
@@ -37,11 +41,21 @@ export function InputGroup({
3741
{label}{' '}
3842
{required && <span className="text-red-500">*</span>}
3943
{info && (
40-
<DefaultTooltip content={info} contentClassName="">
41-
<InformationCircleIcon
42-
className="h-5 w-5 text-neutral-500 ml-1 mb-1"
43-
aria-hidden="true"
44-
/>
44+
<DefaultTooltip
45+
content={info}
46+
contentClassName={contentClassName}
47+
>
48+
{deepInfoIcon ? (
49+
<InformationSolidCircle
50+
className="h-5 w-5 text-neutral-500 ml-1 mb-1 shrink-0"
51+
aria-hidden="true"
52+
/>
53+
) : (
54+
<InformationCircleIcon
55+
className="h-5 w-5 text-neutral-500 ml-1 mb-1 shrink-0"
56+
aria-hidden="true"
57+
/>
58+
)}
4559
</DefaultTooltip>
4660
)}
4761
</span>

deployment/frontend/src/components/_shared/SimpleSelect.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ export default function SimpleSelect<T extends FieldValues, V extends Object>({
3838
id,
3939
onChange: _onChange = (val) => {},
4040
disabled,
41-
}: SimpleSelectProps<T, V> & { onChange?: (val: any) => void, disabled?: boolean }) {
41+
}: SimpleSelectProps<T, V> & {
42+
onChange?: (val: any) => void
43+
disabled?: boolean
44+
}) {
4245
const { control } = formObj ?? useForm()
4346
return (
4447
<Controller

deployment/frontend/src/components/dashboard/_shared/ImageUploader.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ArrowUpTrayIcon } from '@heroicons/react/24/outline'
88
import { MinusCircleIcon } from '@heroicons/react/20/solid'
99
import { Button } from '@/components/_shared/Button'
1010
import { api } from '@/utils/api'
11+
import DefaultTooltip from '@/components/_shared/Tooltip'
1112

1213
export function ImageUploader({
1314
onUploadSuccess,
@@ -16,13 +17,15 @@ export function ImageUploader({
1617
text = 'Upload image',
1718
clearImage,
1819
defaultImage,
20+
tooltip,
1921
}: {
2022
onUploadSuccess: (result: UploadResult) => void
2123
onPresignedUrlSuccess?: (response: string) => void
2224
onUploadStart?: () => void
2325
clearImage?: () => void
2426
text?: string
2527
defaultImage?: string | null
28+
tooltip?: string
2629
}) {
2730
const [key, setKey] = useState<string | null>(null)
2831
const [uploading, setIsUploading] = useState(false)
@@ -101,7 +104,7 @@ export function ImageUploader({
101104
if (onUploadStart) onUploadStart()
102105
})
103106

104-
return (
107+
const uplader = (
105108
<>
106109
<button
107110
onClick={() => uploadInputRef.current?.click()}
@@ -192,4 +195,12 @@ export function ImageUploader({
192195
)}
193196
</>
194197
)
198+
if (tooltip) {
199+
return (
200+
<DefaultTooltip content={tooltip}>
201+
<div>{uplader}</div>
202+
</DefaultTooltip>
203+
)
204+
}
205+
return uplader
195206
}

deployment/frontend/src/components/dashboard/approval/ApprovalRow.tsx

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -286,23 +286,19 @@ function SubCardProfile({
286286
diff.old_dataset?.authors &&
287287
typeof diff.old_dataset?.authors === 'string'
288288
? JSON.parse(diff.old_dataset?.authors)
289-
: diff.old_dataset?.authors ?? []
289+
: (diff.old_dataset?.authors ?? [])
290290
let newAuthors =
291291
diff.new_dataset?.authors &&
292292
typeof diff.new_dataset?.authors === 'string'
293293
? JSON.parse(diff.new_dataset?.authors)
294-
: diff.new_dataset?.authors ?? []
294+
: (diff.new_dataset?.authors ?? [])
295295
diff2 = {
296296
...diff2,
297297
Authors: {
298298
old_value:
299-
oldAuthors?.map(
300-
(a: any) => `${a.name} (${a.email})`
301-
) ?? [],
299+
oldAuthors?.map((a: any) => `${a.name} (${a.email})`) ?? [],
302300
new_value:
303-
newAuthors?.map(
304-
(a: any) => `${a.name} (${a.email})`
305-
) ?? [],
301+
newAuthors?.map((a: any) => `${a.name} (${a.email})`) ?? [],
306302
},
307303
}
308304
}
@@ -317,23 +313,21 @@ function SubCardProfile({
317313
diff.old_dataset?.maintainers &&
318314
typeof diff.old_dataset?.maintainers === 'string'
319315
? JSON.parse(diff.old_dataset?.maintainers)
320-
: diff.old_dataset?.maintainers ?? []
316+
: (diff.old_dataset?.maintainers ?? [])
321317
let newMaintainers =
322318
diff.new_dataset?.maintainers &&
323319
typeof diff.new_dataset?.maintainers === 'string'
324320
? JSON.parse(diff.new_dataset?.maintainers)
325-
: diff.new_dataset?.maintainers ?? []
321+
: (diff.new_dataset?.maintainers ?? [])
326322
diff2 = {
327323
...diff2,
328324
Maintainers: {
329325
old_value:
330-
oldMaintainers?.map(
331-
(a: any) => `${a.name} (${a.email})`
332-
) ?? [],
326+
oldMaintainers?.map((a: any) => `${a.name} (${a.email})`) ??
327+
[],
333328
new_value:
334-
newMaintainers?.map(
335-
(a: any) => `${a.name} (${a.email})`
336-
) ?? [],
329+
newMaintainers?.map((a: any) => `${a.name} (${a.email})`) ??
330+
[],
337331
},
338332
}
339333
}
@@ -382,7 +376,7 @@ function SubCardProfile({
382376
)
383377
? key.replace(
384378
'resources',
385-
'datafiles'
379+
'data files'
386380
)
387381
: key
388382
)}

deployment/frontend/src/components/dashboard/datasets/DatasetRow.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@ import { DefaultTooltip } from '@/components/_shared/Tooltip'
99

1010
function subFields(dataset: WriDataset) {
1111
return [
12-
{
13-
title: 'Status',
14-
description: dataset.technical_notes
15-
? 'RDI Approved'
16-
: 'RDI Pending',
17-
},
1812
{
1913
title: 'Coverage Start',
2014
description: dataset?.temporal_coverage_start,

0 commit comments

Comments
 (0)