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

Add ability to annotate assessment tools #285

Closed
3 tasks done
Tracked by #35
jarmoza opened this issue Dec 20, 2022 · 6 comments
Closed
3 tasks done
Tracked by #35

Add ability to annotate assessment tools #285

jarmoza opened this issue Dec 20, 2022 · 6 comments
Labels
categorization page Epic A collection of issues that are related by topic and can be addressed together.

Comments

@jarmoza
Copy link
Contributor

jarmoza commented Dec 20, 2022

We need to integrate prior functionality for tool groups into the new version of the category-select-table.

For reference for that work, below is the past implementation of tool groups for the annotation tool in addition to store-related code

Steps:

Extra content: https://miro.com/app/board/uXjVNdHBRlE=/?share_link_id=930510418428

categ-toolgroup.vue:

<template>

    <b-row style="margin-bottom: 0;">
        <b-col cols="6">

            <!-- Heading for tool grouping component -->
            <b-row>
                <h3>{{ uiText.title }}</h3>
            </b-row>

            <!-- Instructions prompting the user how to group tools -->
            <b-row>
                <p class="instructions-text">
                    {{ uiText.instructions }}
                </p>
            </b-row>

            <!-- Tool grouping inputs -->

            <!-- Tool group name -->
            <b-row>
                <b-input-group>
                    <label for="tool-name-textbox">Group name:&nbsp;</label>
                    <b-form-input
                        data-cy="toolgroup-name-textbox"
                        id="tool-name-textbox"
                        v-model="newToolGroupName"
                        :placeholder="uiText.toolGroupNamePlaceholder" />
                </b-input-group>
            </b-row>

            <!-- Columns in tool group -->
            <b-row>
                <b-input-group>
                    <label for="column-multiselect">Columns:&nbsp;</label>
                    <b-form-select
                        data-cy="toolgroup-column-multiselect"
                        id="column-multiselect"
                        v-model="selectedTools"
                        multiple
                        :options="columnOptions()"
                        :select-size="4" />
                </b-input-group>
            </b-row>

            <!-- Create new tool group button -->
            <b-row>
                <b-col cols="4">
                    <!-- Error message - only visible if duplicate group name is entered -->
                    <p v-if="invalidNameEntered" class="instructions-text">
                        {{ uiText.alreadyExistsText }}
                    </p>
                </b-col>
                <b-col class="no-padding-right" ols="8">
                    <b-button
                        data-cy="create-toolgroup-button"
                        class="float-right"
                        :disabled="!readyToAddOrModifyToolGroup"
                        variant="info"
                        @click="( modes.create === currentMode ) ? createToolGroup() : modifyToolGroup()">
                        {{ currentModeText() }}
                    </b-button>
                </b-col>
            </b-row>

        </b-col>

        <b-col cols="6">

            <b-row>
                <!-- Tool grouping table -->
                <b-table
                    data-cy="toolgroup-table"
                    bordered
                    selectable
                    head-variant="dark"
                    ref="table"
                    select-mode="single"
                    :items="assessmentToolGroups.items"
                    :fields="assessmentToolGroups.fields"
                    @row-selected="selectTableRow($event)">

                    <!-- Blank row for empty table for aesthetics -->
                    <template #top-row v-if="hasNoGroups">
                        <!-- Adding &nbsp; to the cell so that it maintains the standard cell height -->
                        <td
                            v-for="field in assessmentToolGroups.fields"
                            :key="field.key">
                                &nbsp;
                        </td>
                    </template>

                    <!-- Remove row button -->
                    <template #cell(action)="row">
                        <b-button
                            class="float-right"
                            variant="danger"
                            @click="removeToolGroup(row)">
                            {{ uiText.removeToolGroupButton }}
                        </b-button>
                    </template>

                </b-table>
            </b-row>

        </b-col>

    </b-row>

</template>

<script>

    // Allows for reference to store data by creating simple, implicit getters
    import { mapGetters } from "vuex";

    export default {

        props: {

            columnNames: { type: Array, required: true }
        },

        inject: [

            "columnToCategoryMap",
            "toolGroups"
        ],

        data() {

            return {

                assessmentToolGroups: {

                    items: [],
                    fields: [
                        {
                            key: "name",
                            label: "Group Name",
                            type: "text",
                            placeholder: "Enter Name..."
                        },
                        {
                            key: "toolList",
                            label: "Tools",
                            type: "text"
                        },
                        {
                            key:"action",
                            label: " ",
                            type: "text"
                        }
                    ]
                },

                currentGroup: "",
                currentMode: "create",

                modes: {

                    create: "create",
                    modify: "modify"
                },

                newToolGroupName: "",

                selectedTools: [],

                uiText: {

                    alreadyExistsText: "Group already exists in table",
                    createToolGroupButton: "+ Create Tool Group",
                    instructions: "Enter a name, choose columns, and then click the 'create' button",
                    modifyToolGroupbutton: "~ Modify Tool Group",
                    removeToolGroupButton: "x Remove Tool Group",
                    title: "Assessment Tool Groups",
                    toolGroupNamePlaceholder: "Type name of assessment tool group here..."
                }
            };
        },

        computed: {

            ...mapGetters([

                "getGroupOfTool",
                "isToolGrouped"
            ]),

            hasNoGroups() {

                // Table is blank if it has just the default blank entry
                return ( 0 === this.assessmentToolGroups.items.length );
            },

            invalidNameEntered() {

                // Name cannot be a duplicate of another tool group
                let invalid = ( this.newToolGroupName in this.toolGroups );

                // With the exception of the current group being edited while in 'modify' mode
                if ( this.modes.modify === this.currentMode ) {
                    invalid = invalid && this.currentGroup !== this.newToolGroupName;
                }

                return invalid;
            },

            readyToAddOrModifyToolGroup() {

                // 1. Minimum ready condition check

                // A. The entered group name must not be blank and there must be
                // at least one tool selected
                let ready = ( "" !== this.newToolGroupName &&
                    this.selectedTools.length > 0 );

                // B. Short-circuit out if this condition is not met
                if ( !ready ) {
                    return false;
                }

                // 2. Mode-specific additional condition check
                switch ( this.currentMode ) {

                    case this.modes.create:

                        // No duplicate group names allowed
                        ready = ( ready &&
                            !Object.hasOwn(this.toolGroups, this.newToolGroupName) );
                        break;

                    case this.modes.modify:

                        // No duplicate group names, but skip check for the
                        // name of the group currently being modified
                        for ( const groupName in this.toolGroups ) {

                            if ( groupName === this.newToolGroupName &&
                                groupName !== this.currentGroup ) {
                                ready = false;
                                break;
                            }
                        }
                        break;
                }

                return ready;
            }
        },

        watch: {

            columnToCategoryMap: {

                deep: true,
                handler(p_newColumnToCategoryMap, p_oldColumnToCategoryMap) {

                    // Note: `newValue` will be equal to `oldValue` here
                    // on nested mutations as long as the object itself
                    // hasn't been replaced.

                    // 1. Check to see if any columns have been unlinked as assessment tools
                    const toBeRemoved = [];
                    for ( const item of this.assessmentToolGroups.items ) {

                        const toolArray = item.toolList.split(", ");
                        for ( const tool of toolArray ) {

                            if ( null === this.columnToCategoryMap[tool] ) {
                                toBeRemoved.push(tool);
                            }
                        }
                    }

                    // 2. Remove the recently unlinked columns from the tool groups in the store
                    for ( const groupName in this.toolGroups ) {
                        for ( const column of toBeRemoved ) {
                            if ( this.toolGroups[groupName].includes(column) ) {

                                this.$emit("remove-tool-from-group", {
                                    tool: column,
                                    group: groupName
                                });
                            }
                        }
                    }

                    // 3. If any tool group is empty, remove it from the store
                    for ( const groupName in this.toolGroups ) {
                        if ( 0 === this.toolGroups[groupName].length ) {

                            this.removeToolGroup({ item: { name: groupName }});
                        }
                    }

                    // 4. Update the tool group data with changes
                    this.refreshToolGroupTable();
                }
            }
        },

        mounted() {

            // Initialize the interface based on the contents of toolGroups in
            // the store by populating the tool group table
            this.refreshToolGroupTable();
        },

        methods: {

            columnOptions() {

                const options = [];

                // 1. Determine availability of columns for selection
                for ( const columnName in this.columnToCategoryMap ) {

                    if ( "Assessment Tool" == this.columnToCategoryMap[columnName] ) {

                        let disabledStatus = true;

                        // A. Columns are available if they are not already grouped,
                        // OR if in 'modify' mode if they are part of the
                        // current group being modified
                        if ( (this.modes.modify === this.currentMode &&
                            this.currentGroup === this.getGroupOfTool(columnName)) ||
                            !this.isToolGrouped(columnName) ) {

                            disabledStatus = false;
                        }

                        // B. Note that available columns are added in the order
                        // they are listed in the column linking table on the
                        // categorization page
                        options.push({

                            disabled: disabledStatus,
                            text: columnName,
                            value: columnName
                        });
                    }
                }

                return options;
            },

            currentModeText() {

                return ( this.modes.create === this.currentMode ) ?
                    this.uiText.createToolGroupButton : this.uiText.modifyToolGroupbutton;
            },

            createToolGroup() {

                // 1. Create a new entry for a new tool group
                this.assessmentToolGroups.items.push({

                    name: this.newToolGroupName,
                    toolList: this.selectedTools.join(", ")
                });

                // 2. Tell the categorization page a new tool group has been created
                this.$emit("tool-group-action", {

                    action: "createToolGroup",
                    data: {
                        name: this.newToolGroupName,
                        tools: this.selectedTools
                    }
                });

                // 3. Mode stays in 'create' but input fields are cleared
                this.switchMode(this.modes.create, {});
            },

            modifyToolGroup() {

                // 1. Find item in table data and modify it
                const groupIndex = this.assessmentToolGroups.items.findIndex(groupData =>
                    this.currentGroup === groupData.name);
                this.$set(this.assessmentToolGroups.items[groupIndex], "name", this.newToolGroupName);
                this.$set(this.assessmentToolGroups.items[groupIndex], "toolList", this.selectedTools.join(", "));

                // 2. Tell the categorization page a tool group has been modified
                this.$emit("tool-group-action", {

                    action: "modifyToolGroup",
                    data: {

                        name: this.newToolGroupName,
                        previousName: this.currentGroup,
                        tools: this.selectedTools
                    }
                });

                // 3. Switch back to 'create' mode
                this.switchMode(this.modes.create, {});

                // 4. Deselect table row
                this.$refs.table.clearSelected();
            },

            refreshToolGroupTable() {

                // 1. Make sure the items list is clear
                this.assessmentToolGroups.items = [];

                // 2. Build the tool group table data based on the current store values
                for ( const toolGroup in this.toolGroups ) {

                    this.assessmentToolGroups.items.push({

                        name: toolGroup,
                        toolList: this.toolGroups[toolGroup].join(", ")
                    });
                }
            },

            removeToolGroup(p_row) {

                // 1. Clear this group's row in the table
                let groupIndex = this.assessmentToolGroups.items.findIndex(x => x.name === p_row.item.name);
                this.assessmentToolGroups.items.splice(groupIndex, 1);

                // 2. Remove this group from the tool group map
                this.$emit("tool-group-action", {

                    action: "removeToolGroup",
                    data: {
                        name: p_row.item.name
                    }
                });
            },

            switchMode(p_mode, p_data) {

                if ( this.modes.create === p_mode ) {

                    // 1. Clear the tool group input fields
                    this.newToolGroupName = "";
                    this.selectedTools = [];

                    // 2. Set the mode back to 'create'
                    this.currentMode = this.modes.create;

                    // 3. Clear field indicating current group being edited
                    this.currentGroup = "";
                } else if ( this.modes.modify === p_mode ) {

                    // 1. Set mode to 'modify' mode
                    this.currentMode = this.modes.modify;

                    // 2. Save the group name for saving edits later
                    this.currentGroup = p_data.name;

                    // 3. Populate textbox with selected group's name
                    this.newToolGroupName = p_data.name;
                }
            },

            selectTableRow(p_eventData) {

                // Deselecting a table row switches to 'create' mode
                if ( 0 === p_eventData.length ) {
                    this.switchMode(this.modes.create, {});
                }
                // Selecting a table row switches to 'modify' mode
                else {
                    this.switchMode(this.modes.modify, p_eventData[0]);
                }
            }
        }
    };

</script>

<style>

    .no-padding-right {

        padding-right: 0;
    }

</style>

Store-related code:

            removeToolGroup(p_toolGroupData) {

                // Remove this tool group from the store
                this.$store.dispatch("removeToolGroup", p_toolGroupData);
            },

            saveNewToolGroup(p_toolGroupData) {

                // Create this new tool group in the store
                this.$store.dispatch("createToolGroup", p_toolGroupData);
            },

            setSelectedCategory(p_clickData) {

                // Save the name of the selected category
                this.selectedCategory = p_clickData.category;
            },

            toolGroupAction(p_event) {

                // Create, modify, or remove this tool group in the store
                this.$store.dispatch(p_event.action, p_event.data);
            }
@jarmoza jarmoza added categorization page feature:enhancement refactor Simplifying or restructuring existing code or documentation. labels Dec 20, 2022
@jarmoza jarmoza moved this to Inbox in Neurobagel Dec 20, 2022
@github-actions
Copy link

We want to keep our issues up to date and active. This issue hasn't seen any activity in the last 30 days.
We have applied the stale-issue label to indicate that this issue should be reviewed again and then either prioritized or closed.

@github-actions github-actions bot added the _flag:stale [BOT ONLY] Flag issue that hasn't been updated in a while and needs to be triaged again label Jan 20, 2023
@jarmoza jarmoza removed the _flag:stale [BOT ONLY] Flag issue that hasn't been updated in a while and needs to be triaged again label Jan 20, 2023
@github-actions
Copy link

We want to keep our issues up to date and active. This issue hasn't seen any activity in the last 30 days.
We have applied the stale-issue label to indicate that this issue should be reviewed again and then either prioritized or closed.

@github-actions github-actions bot added the _flag:stale [BOT ONLY] Flag issue that hasn't been updated in a while and needs to be triaged again label Feb 20, 2023
@jarmoza
Copy link
Contributor Author

jarmoza commented Feb 20, 2023

Un-stale-ing this putting into ready for the next Milestone cycle. This code has a few useful pieces that may be repurposed for the new custom category feature.

@jarmoza jarmoza moved this from Inbox to Ready in Neurobagel Feb 20, 2023
@jarmoza jarmoza removed the _flag:stale [BOT ONLY] Flag issue that hasn't been updated in a while and needs to be triaged again label Feb 20, 2023
@surchs surchs removed the status in Neurobagel Mar 21, 2023
@surchs surchs moved this to Backlog in Neurobagel Mar 21, 2023
@github-actions
Copy link

We want to keep our issues up to date and active. This issue hasn't seen any activity in the last 30 days.
We have applied the stale-issue label to indicate that this issue should be reviewed again and then either prioritized or closed.

@github-actions github-actions bot added the _flag:stale [BOT ONLY] Flag issue that hasn't been updated in a while and needs to be triaged again label Mar 23, 2023
@surchs surchs removed the status in Neurobagel Apr 19, 2023
@github-actions
Copy link

We want to keep our issues up to date and active. This issue hasn't seen any activity in the last 30 days.
We have applied the stale-issue label to indicate that this issue should be reviewed again and then either prioritized or closed.

@surchs surchs added feat:add and removed feat:improve refactor Simplifying or restructuring existing code or documentation. _flag:stale [BOT ONLY] Flag issue that hasn't been updated in a while and needs to be triaged again labels Oct 10, 2023
@surchs surchs changed the title Revisit how tool groups are added, modified, deleted on the categorization page Add ability to annotate assessment tools Oct 10, 2023
@surchs surchs moved this to Backlog in Neurobagel Oct 12, 2023
@surchs surchs added the Epic A collection of issues that are related by topic and can be addressed together. label Oct 12, 2023
@rmanaem
Copy link
Contributor

rmanaem commented Oct 25, 2023

I think we can close this. wdyt @surchs?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
categorization page Epic A collection of issues that are related by topic and can be addressed together.
Projects
Status: Review - Done
Development

No branches or pull requests

3 participants