-
Notifications
You must be signed in to change notification settings - Fork 127
Fixes #2290 Improve the UX of the clone operation #2311
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
base: master
Are you sure you want to change the base?
Changes from all commits
c8378f8
3233d34
b103a8c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| /* | ||
| * Copyright 2025, GeoSolutions Sas. | ||
| * All rights reserved. | ||
| * | ||
| * This source code is licensed under the BSD-style license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
|
|
||
| import React, { useEffect, useMemo, useRef } from 'react'; | ||
| import { connect } from 'react-redux'; | ||
| import { createSelector } from 'reselect'; | ||
| import { createPlugin } from '@mapstore/framework/utils/PluginsUtils'; | ||
| import { userSelector } from '@mapstore/framework/selectors/security'; | ||
| import { getResources } from '@mapstore/framework/plugins/ResourcesCatalog/selectors/resources'; | ||
|
|
||
| import { startAsyncProcess } from '@js/actions/resourceservice'; | ||
| import { extractExecutionsFromResources, ProcessTypes } from '@js/utils/ResourceServiceUtils'; | ||
| import { getResourceData } from '@js/selectors/resource'; | ||
| import isEmpty from 'lodash/isEmpty'; | ||
| import { getCurrentProcesses } from '@js/selectors/resourceservice'; | ||
| import FlexBox from '@mapstore/framework/components/layout/FlexBox'; | ||
| import Spinner from '@mapstore/framework/components/layout/Spinner'; | ||
| import Message from '@mapstore/framework/components/I18N/Message'; | ||
|
|
||
| /** | ||
| * Plugin that monitors async executions embedded in resources and | ||
| * triggers the executions API using the existing resourceservice epics. | ||
| * | ||
| * It reads `resources[*].executions` and, when it finds executions and, | ||
| * dispatches `startAsyncProcess({ resource, output, processType })` once per execution. | ||
| * | ||
| */ | ||
| function ExecutionTracker({ | ||
| resources, | ||
| user, | ||
| onStartAsyncProcess, | ||
| resourceData, | ||
| processes | ||
| }) { | ||
| const startedKeys = useRef(new Set()); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this file we are tracking both resources and resourceData but this plugin is only used in dataset pages where only resourceData is available. Please remove resources if not needed, instead if needed could you explain why? |
||
| const redirected = useRef(false); | ||
|
|
||
| useEffect(() => { | ||
| const username = user?.info?.preferred_username; | ||
| const resourcesToTrack = !isEmpty(resourceData) ? [...resources, resourceData] : resources; | ||
| if (!resourcesToTrack?.length || !username) { | ||
| return; | ||
| } | ||
| const executions = extractExecutionsFromResources(resourcesToTrack, username) || []; | ||
| if (!executions.length) { | ||
| return; | ||
| } | ||
| executions.forEach((process) => { | ||
| const pk = process?.resource?.pk ?? process?.resource?.id; | ||
| const processType = process?.processType; | ||
| const statusUrl = process?.output?.status_url; | ||
| if (!pk || !processType || !statusUrl) { | ||
| return; | ||
| } | ||
| const key = `${pk}:${processType}:${statusUrl}`; | ||
| if (!startedKeys.current.has(key)) { | ||
| startedKeys.current.add(key); | ||
| onStartAsyncProcess(process); | ||
| } | ||
| }); | ||
| }, [resources, user, onStartAsyncProcess, resourceData]); | ||
|
|
||
| useEffect(() => { | ||
| if (redirected.current) { | ||
| return; | ||
| } | ||
| const resourcePk = resourceData?.pk ?? resourceData?.id; | ||
| if (!resourcePk) { | ||
| return; | ||
| } | ||
| const clonedResourceUrl = (processes || []) | ||
| .find((p) => p?.resource?.pk === resourcePk && !!p?.clonedResourceUrl) | ||
| ?.clonedResourceUrl; | ||
|
|
||
| if (clonedResourceUrl && window?.location?.href !== clonedResourceUrl) { | ||
| redirected.current = true; | ||
| window.location.assign(clonedResourceUrl); | ||
| } | ||
| }, [processes, resourceData]); | ||
|
|
||
| const msgId = useMemo(() => { | ||
| if (isEmpty(resourceData)) { | ||
| return null; | ||
| } | ||
| const resourcePk = resourceData?.pk ?? resourceData?.id; | ||
| if (!resourcePk) { | ||
| return null; | ||
| } | ||
| const foundProcess = processes.filter((p) => p?.resource?.pk === resourcePk); | ||
| if (!foundProcess?.length) { | ||
| return null; | ||
| } | ||
| const copying = foundProcess.some((p) => [ProcessTypes.COPY_RESOURCE, 'copy', 'copy_geonode_resource'].includes(p?.processType)); | ||
| const deleting = foundProcess.some((p) => [ProcessTypes.DELETE_RESOURCE, 'delete'].includes(p?.processType)); | ||
| if (copying) { | ||
| return 'gnviewer.cloning'; | ||
| } | ||
| if (deleting) { | ||
| return 'gnviewer.deleting'; | ||
| } | ||
| return null; | ||
| }, [processes, resourceData]); | ||
|
|
||
| return msgId ? ( | ||
| <div className="gn-execution-tracker"> | ||
| <FlexBox centerChildren gap="sm" className="ms-text _font-size-lg _strong"> | ||
| <Spinner /> | ||
| <Message msgId={msgId} /> | ||
| </FlexBox> | ||
| </div> | ||
| ) : null; | ||
| } | ||
|
|
||
| const ExecutionTrackerPlugin = connect( | ||
| createSelector( | ||
| [ | ||
| (state) => getResources(state, { id: 'catalog' }), | ||
| userSelector, | ||
| getResourceData, | ||
| getCurrentProcesses | ||
| ], | ||
| (resources, user, resourceData, processes) => ({ | ||
| resources, | ||
| user, | ||
| resourceData, | ||
| processes | ||
| }) | ||
| ), | ||
| { | ||
| onStartAsyncProcess: startAsyncProcess | ||
| } | ||
| )(ExecutionTracker); | ||
|
|
||
| export default createPlugin('ExecutionTracker', { | ||
| component: ExecutionTrackerPlugin | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,7 +55,7 @@ export const extractExecutionsFromResources = (resources, username) => { | |
| status_url: statusUrl, | ||
| user | ||
| }) => | ||
| funcName === 'copy' | ||
| ['copy', 'copy_geonode_resource'].includes(funcName) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we support also delete now that all dirty state resources are visible (are there other processes we should support?) |
||
| && statusUrl && user && user === username | ||
| ).map((output) => { | ||
| return { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| #ms-components-theme(@theme-vars) { | ||
| .gn-main-execution-container { | ||
| .background-color-var(@theme-vars[main-variant-bg]); | ||
| .gn-execution-tracker-content { | ||
| .background-color-var(@theme-vars[main-variant-bg]); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| .gn-execution-tracker { | ||
| position: absolute; | ||
| z-index: 5000; | ||
| width: 100%; | ||
| height: 100%; | ||
| top: 0; | ||
| left: 0; | ||
| background-color: rgba(0, 0, 0, 0.85); | ||
| color: #eeeeee; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,8 @@ | |
| @import '~font-awesome/css/font-awesome.min.css'; | ||
| @import '~ol/ol.css'; | ||
|
|
||
| @import '_execution-tracker.less'; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the other files are imported in alphabetical order above, please add this to the correct position |
||
|
|
||
| :root { | ||
| font-size: 16px; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like that we don't change client side the resources. We should show all resources returned, instead let's add a new case to support
nullresource types. Please remove filteredResources and use resources instead