mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 12:27:07 +00:00
Add project cloning option to ProjectList
This commit is contained in:
@@ -67,9 +67,26 @@
|
||||
<v-list-item-icon><v-icon>mdi-file-document-edit-outline</v-icon></v-list-item-icon>
|
||||
<v-list-item-title class="warning--text">Edit project settings</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item @click="cloneDialogOpen = true">
|
||||
<v-list-item-icon><v-icon>mdi-sheep</v-icon></v-list-item-icon>
|
||||
<v-list-item-title class="warning--text">Clone project</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<v-dialog
|
||||
v-model="cloneDialogOpen"
|
||||
max-width="600"
|
||||
>
|
||||
<dougal-project-settings-name-id-rootpath
|
||||
v-model="cloneProjectDetails"
|
||||
@input="cloneProject"
|
||||
@close="cloneDialogOpen = false"
|
||||
>
|
||||
</dougal-project-settings-name-id-rootpath>
|
||||
</v-dialog>
|
||||
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
@@ -81,10 +98,15 @@ td p:last-of-type {
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import DougalProjectSettingsNameIdRootpath from '@/components/project-settings/name-id-rootpath'
|
||||
|
||||
export default {
|
||||
name: "ProjectList",
|
||||
|
||||
components: {
|
||||
DougalProjectSettingsNameIdRootpath
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
headers: [
|
||||
@@ -120,8 +142,17 @@ export default {
|
||||
items: [],
|
||||
options: {},
|
||||
|
||||
// Whether or not to show archived projects
|
||||
showArchived: true,
|
||||
|
||||
// Cloned project stuff (admin only)
|
||||
cloneDialogOpen: false,
|
||||
cloneProjectDetails: {
|
||||
name: null,
|
||||
id: null,
|
||||
path: null
|
||||
},
|
||||
|
||||
// Context menu stuff
|
||||
contextMenuShow: false,
|
||||
contextMenuX: 0,
|
||||
@@ -187,7 +218,136 @@ export default {
|
||||
this.$nextTick( () => this.contextMenuShow = true );
|
||||
},
|
||||
|
||||
...mapActions(["api"])
|
||||
|
||||
async cloneProject () {
|
||||
|
||||
/* Plan of action:
|
||||
* 1. Pop up dialogue asking for new project name, ID and root path
|
||||
* 2. Get source project configuration
|
||||
* 3. Blank out non-clonable parameters (ASAQC, …)
|
||||
* 4. Rewrite paths prefixed with source rootPath with dest rootPath
|
||||
* 5. Replace name, ID and rootPath
|
||||
* 6. Set archived=true
|
||||
* 7. POST new project
|
||||
* 8. Redirect to new project settings page
|
||||
*/
|
||||
|
||||
// 1. Pop up dialogue asking for new project name, ID and root path
|
||||
// (already done, that's why we're here)
|
||||
|
||||
const pid = this.contextMenuItem?.pid;
|
||||
|
||||
if (!pid) return;
|
||||
|
||||
const tpl = this.cloneProjectDetails; // Shorter
|
||||
|
||||
if (!tpl.id || !tpl.name || !tpl.path) {
|
||||
this.showSnack(["Missing project details. Cannot proceed", "warning"]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.cloneDialogOpen = false;
|
||||
|
||||
/** Drills down an object and applies function fn(obj, key)
|
||||
* on each recursive property [...keys].
|
||||
*/
|
||||
function drill (obj, keys, fn) {
|
||||
if (obj) {
|
||||
if (Array.isArray(keys)) {
|
||||
if (keys.length) {
|
||||
const key = keys.shift();
|
||||
|
||||
if (keys.length == 0 && key != "*") { // After shift()
|
||||
if (typeof fn == "function") {
|
||||
fn(obj, key);
|
||||
}
|
||||
}
|
||||
|
||||
if (key == "*") {
|
||||
// Iterate through this object's keys
|
||||
if (keys.length) {
|
||||
for (const k in obj) {
|
||||
drill(obj[k], [...keys], fn);
|
||||
}
|
||||
} else {
|
||||
for (const k in obj) {
|
||||
drill(obj, [k], fn);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
drill(obj[key], keys, fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Get source project configuration
|
||||
const src = await this.api([`/project/${pid}/configuration`]);
|
||||
|
||||
const blankList = [ "id", "name", "schema", "asaqc.id", "archived" ];
|
||||
|
||||
const prjPaths = [
|
||||
"preplots.*.path",
|
||||
"raw.p111.paths.*",
|
||||
"raw.p190.paths.*",
|
||||
"raw.smsrc.paths.*",
|
||||
"final.p111.paths.*",
|
||||
"final.p190.paths.*",
|
||||
"qc.definitions",
|
||||
"qc.parameters",
|
||||
"imports.map.layers.*.*.path",
|
||||
"rootPath" // Needs to go last because of lazy replacer() implementation below
|
||||
];
|
||||
|
||||
// Technically don't need this
|
||||
const deleter = (obj, key) => {
|
||||
delete obj[key];
|
||||
}
|
||||
|
||||
const replacer = (obj, key) => {
|
||||
if (src.rootPath && tpl.path) {
|
||||
if (obj[key].startsWith(src.rootPath)) {
|
||||
obj[key] = obj[key].replace(src.rootPath, tpl.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Blank out non-clonable parameters (ASAQC, …)
|
||||
blankList.forEach( i => drill(src, i.split("."), deleter) );
|
||||
|
||||
// 4. Rewrite paths prefixed with source rootPath with dest rootPath
|
||||
prjPaths.forEach( i => drill(src, i.split("."), replacer) );
|
||||
|
||||
// 5. Replace name, ID and rootPath
|
||||
// Could use deepMerge, but meh!
|
||||
src.name = tpl.name;
|
||||
src.id = tpl.id;
|
||||
|
||||
// 6. Set archived=true
|
||||
src.archived = true;
|
||||
|
||||
// 7. POST new project
|
||||
|
||||
const init = {
|
||||
method: "POST",
|
||||
body: src
|
||||
};
|
||||
const cb = (err, res) => {
|
||||
if (!err && res) {
|
||||
if (res.status == "201") {
|
||||
// 8. Redirect to new project settings page
|
||||
const settingsUrl = `/projects/${tpl.id.toLowerCase()}/configuration`;
|
||||
this.$router.push(settingsUrl);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
await this.api(["/project", init, cb]);
|
||||
|
||||
},
|
||||
|
||||
...mapActions(["api", "showSnack"])
|
||||
},
|
||||
|
||||
mounted () {
|
||||
|
||||
Reference in New Issue
Block a user