Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 12 additions & 1 deletion src/components/map-projects/Controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import TimelineIcon from '@mui/icons-material/Timeline';
import SettingsIcon from '@mui/icons-material/Settings';
import SaveIcon from '@mui/icons-material/Save';
import DeleteIcon from '@mui/icons-material/Delete';
import CopyIcon from '@mui/icons-material/CopyAll';
import JSONIcon from '@mui/icons-material/DataObject';
import { copyToClipboard } from '../../common/utils'
import RepoIcon from '../repos/RepoIcon'
Expand All @@ -38,7 +39,7 @@ const IkonButton = ({title, icon, onClick, color, disabled, id}) => {
)
}

const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving, onImport, importResponse, onDownloadImportReport, onProjectLogsClick, isProjectsLogOpen, configure, setConfigure, isCoreUser, loadingMatches}) => {
const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving, onImport, importResponse, onDownloadImportReport, onProjectLogsClick, isProjectsLogOpen, configure, setConfigure, isCoreUser, loadingMatches, onCopyClick}) => {
const { t } = useTranslation();
const [anchorEl, setAnchorEl] = React.useState(null);
const downloadOpen = Boolean(anchorEl);
Expand Down Expand Up @@ -76,6 +77,16 @@ const Controls = ({project, onDownload, onSave, onDelete, owner, file, isSaving,
icon={<DownloadIcon />}
disabled={loadingMatches}
/>
{
project?.id &&
<IkonButton
id='copy-button'
color='secondary'
onClick={onCopyClick}
title={t('map_project.copy_project')}
icon={<CopyIcon />}
/>
}
{
project?.id &&
<IkonButton
Expand Down
46 changes: 44 additions & 2 deletions src/components/map-projects/MapProject.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import moment from 'moment'
import Split from 'react-split';
import BridgeMatch from '../../services/LazyLoader'

import { useParams, useHistory } from 'react-router-dom'
import { useParams, useHistory, useLocation } from 'react-router-dom'

import Paper from '@mui/material/Paper'
import Button from '@mui/material/Button';
Expand Down Expand Up @@ -122,6 +122,12 @@ const MapProject = () => {
const user = getCurrentUser()
const params = useParams()
const history = useHistory()
const location = useLocation()
const copyFromProjectURL = React.useMemo(() => {
const queryParams = new URLSearchParams(location.search)
return queryParams.get('copyFrom')
}, [location.search])

const bridgeRef = React.useRef()
const facetsRequestsRef = React.useRef({})
const latestFacetRequestRef = React.useRef({})
Expand Down Expand Up @@ -304,10 +310,15 @@ const MapProject = () => {

React.useEffect(() => {
fetchMapTypes()
fetchAIModels()

if(params.projectId && params.owner) {
fetchAndSetProject()
return
}
if(copyFromProjectURL) {
copyFromProject()
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copyFromProjectURL is read inside this effect but missing from deps. If mount-only is intentional, add // eslint-disable-next-line react-hooks/exhaustive-deps with a one-line reason.

fetchAIModels()
}, [])

React.useEffect(() => {
Expand All @@ -324,6 +335,27 @@ const MapProject = () => {
}
}, [repoVersion, project])


const copyFromProject = () => {
setLoadingProject(true)
APIService.new().overrideURL(copyFromProjectURL).appendToUrl('configurations/').get().then(response => {
const copiedProject = response.data
setProject(null)
setFilters(copiedProject.filters || {})
setLookupConfig(copiedProject.lookup_config || {})
setCandidatesScore(copiedProject.score_configuration || {recommended: 99, available: 70})
setRetired(Boolean(copiedProject.include_retired || false))
setAlgosSelected(copiedProject.algorithms || [])
setEncoderModel(copiedProject.encoder_model || DEFAULT_ENCODER_MODEL)
if(copiedProject.target_repo_url) {
const repoParams = URIToParentParams(copiedProject.target_repo_url, true)
fetchRepo(dropVersion(copiedProject.target_repo_url))
fetchVersions(copiedProject.target_repo_url, repoParams?.repoVersion || 'HEAD')
}
setConfigure(true)
}).finally(() => setLoadingProject(false))
}

const fetchAndSetProject = () => {
setLoadingProject(true)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefill name with "Copy of {original name}" so users don't land on a blank field. Requires adding name to the /configurations/ API response.

let url = ['', params.ownerType, params.owner, 'map-projects', params.projectId, ''].join('/')
Expand Down Expand Up @@ -2707,6 +2739,15 @@ const MapProject = () => {
/>
)


const onCopyClick = event => {
event.preventDefault()
event.stopPropagation()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should open in the same tab to match the listing-page button.

Suggested change
event.stopPropagation()
history.push(`/map-projects/new?copyFrom=${encodeURIComponent(project.url)}`)

if(project?.url) {
window.open(`/#/map-projects/new?copyFrom=${encodeURIComponent(project.url)}`, '_blank', 'noopener,noreferrer')
}
}

return permissionDenied ? <Error403/> : (
<div className='col-xs-12 padding-0' style={{borderRadius: '10px', width: 'calc(100vw - 32px)'}}>
{
Expand Down Expand Up @@ -2860,6 +2901,7 @@ const MapProject = () => {
isProjectsLogOpen={showProjectLogs}
configure={configure}
setConfigure={setConfigure}
onCopyClick={onCopyClick}
/>
}
</div>
Expand Down
25 changes: 22 additions & 3 deletions src/components/map-projects/MapProjects.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import React from 'react'
import { useHistory } from 'react-router-dom'
import { useTranslation } from 'react-i18next';

import moment from 'moment'
import reject from 'lodash/reject'
import Button from '@mui/material/Button'
Expand All @@ -12,21 +15,24 @@ import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Skeleton from '@mui/material/Skeleton';
import ListItemText from '@mui/material/ListItemText'
import Tooltip from '@mui/material/Tooltip'
import AddIcon from '@mui/icons-material/Add'
import times from 'lodash/times'
import APIService from '../../services/APIService'
import { getCurrentUser } from '../../common/utils'
import OwnerIcon from '../common/OwnerIcon'
import NoResults from '../search/NoResults';
import MapProjectDeleteConfirmDialog from './MapProjectDeleteConfirmDialog';
import { useTranslation } from 'react-i18next';

const MapProjects = () => {
const { t } = useTranslation();
const history = useHistory()

const user = getCurrentUser()
const [loading, setLoading] = React.useState([])
const [projects, setProjects] = React.useState([])
const [deleteProject, setDeleteProject] = React.useState(null)

const fetchProjects = () => {
fetchUserProjects()
fetchOrgProjects()
Expand All @@ -53,6 +59,14 @@ const MapProjects = () => {
setDeleteProject(null)
}

const onCopyClick = (event, project) => {
event.preventDefault()
event.stopPropagation()
if(project?.url) {
history.push(`/map-projects/new?copyFrom=${encodeURIComponent(project.url)}`)
}
}

const isSplitView = false
const loaded = loading.length === 2
return (
Expand Down Expand Up @@ -141,10 +155,15 @@ const MapProjects = () => {
</TableCell>
<TableCell align='right'>
<span style={{display: 'flex', alignItems: 'center'}}>
<Button size='small' color='primary' variant='contained' sx={{textTransform: 'none'}} href={`#${project.url}`}>
<Button size='small' color='primary' variant='contained' sx={{textTransform: 'none', margin: '0 4px'}} href={`#${project.url}`}>
{t('common.open')}
</Button>
<Button size='small' color='error' variant='text' sx={{marginLeft: '8px', textTransform: 'none'}} onClick={() => setDeleteProject(project)}>
<Tooltip title={t('map_project.copy_project')}>
<Button size='small' variant='contained' color='secondary' sx={{margin: '0 4px', textTransform: 'none'}} onClick={event => onCopyClick(event, project)}>
{t('common.copy')}
</Button>
</Tooltip>
<Button size='small' color='error' variant='text' sx={{margin: '0 4px', textTransform: 'none'}} onClick={() => setDeleteProject(project)}>
{t('common.delete')}
</Button>
Comment on lines +158 to 168
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix indentation inconsistency.

Suggested change
<Button size='small' color='primary' variant='contained' sx={{textTransform: 'none', margin: '0 4px'}} href={`#${project.url}`}>
{t('common.open')}
</Button>
<Button size='small' color='error' variant='text' sx={{marginLeft: '8px', textTransform: 'none'}} onClick={() => setDeleteProject(project)}>
<Tooltip title={t('map_project.copy_project')}>
<Button size='small' variant='contained' color='secondary' sx={{margin: '0 4px', textTransform: 'none'}} onClick={event => onCopyClick(event, project)}>
{t('common.copy')}
</Button>
</Tooltip>
<Button size='small' color='error' variant='text' sx={{margin: '0 4px', textTransform: 'none'}} onClick={() => setDeleteProject(project)}>
{t('common.delete')}
</Button>
<Button size='small' color='primary' variant='contained' sx={{margin: '0 4px', textTransform: 'none'}} href={`#${project.url}`}>
{t('common.open')}
</Button>
<Tooltip title={t('map_project.copy_project')}>
<Button size='small' variant='contained' color='secondary' sx={{margin: '0 4px', textTransform: 'none'}} onClick={event => onCopyClick(event, project)}>
{t('common.copy')}
</Button>
</Tooltip>
<Button size='small' color='error' variant='text' sx={{margin: '0 4px', textTransform: 'none'}} onClick={() => setDeleteProject(project)}>
{t('common.delete')}
</Button>

</span>
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/en/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"draft": "Draft",
"highlights": "Highlights",
"click_to_copy": "Click to Copy",
"copy": "Copy",
"submit": "Submit",
"external_id": "External ID",
"access_level": "Access Level",
Expand Down Expand Up @@ -589,7 +590,8 @@
"alternates": "Alternates",
"model": "Model",
"bridge_source_url": "Bridge Source URL",
"bridge_source_url_description": "The interface terminology to search through for bridge matching"
"bridge_source_url_description": "The interface terminology to search through for bridge matching",
"copy_project": "Creates a new project by copying this project's configuration only. No candidates, decisions, or input files will be copied."
},
"app": {
"web_version": "Web Version",
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/es/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"draft": "Borrador",
"highlights": "Destacados",
"click_to_copy": "Clic para copiar",
"copy": "Copiar",
"submit": "Enviar",
"external_id": "ID externo",
"access_level": "Nivel de acceso",
Expand Down Expand Up @@ -564,7 +565,8 @@
"alternates": "Alternativas",
"model": "Modelo",
"bridge_source_url": "URL de la fuente de Bridge",
"bridge_source_url_description": "La terminología de interfaz para buscar candidatos de coincidencia"
"bridge_source_url_description": "La terminología de interfaz para buscar candidatos de coincidencia",
"copy_project": "Crea un nuevo proyecto copiando solo la configuración de este proyecto. No se copiarán candidatos, decisiones ni archivos de entrada."
},
"app": {
"web_version": "Versión Web",
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/locales/zh/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"draft": "草案",
"highlights": "亮点",
"click_to_copy": "单击复制",
"copy": "复制",
"submit": "提交",
"external_id": "外部 ID",
"access_level": "访问权限级别",
Expand Down Expand Up @@ -589,7 +590,8 @@
"alternates": "备选",
"model": "模型",
"bridge_source_url": "桥接源 URL",
"bridge_source_url_description": "用于搜索匹配候选对象的接口术语"
"bridge_source_url_description": "用于搜索匹配候选对象的接口术语",
"copy_project": "通过仅复制此项目的配置来创建一个新项目。不会复制候选项、决策或输入文件。"
},
"app": {
"web_version": "Web 版本",
Expand Down