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

Refactor ID Validation Logic into Utility Function #896

Merged
merged 3 commits into from
Oct 8, 2024
Merged
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
35 changes: 7 additions & 28 deletions webapp/src/components/BatchCreateItemModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Modal
:model-value="modelValue"
:disable-submit="
itemIDValidationMessages.some((e) => e) ||
isValidEntryID.some((e) => e) ||
(!generateIDsAutomatically && items.some((s) => !Boolean(s.item_id)))
"
@update:model-value="$emit('update:modelValue', $event)"
Expand Down Expand Up @@ -277,7 +277,7 @@
</tr>
<td colspan="3">
<!-- eslint-disable-next-line vue/no-v-html -->
<span class="form-error" v-html="itemIDValidationMessages[index]" />
<span class="form-error" v-html="isValidEntryID[index]" />
</td>
</template>
</tbody>
Expand Down Expand Up @@ -332,6 +332,7 @@
import Modal from "@/components/Modal.vue";
import ItemSelect from "@/components/ItemSelect.vue";
import { createNewSamples } from "@/server_fetch_utils.js";
import { validateEntryID } from "@/field_utils.js";
import { itemTypes, SAMPLE_TABLE_TYPES } from "@/resources.js";
export default {
name: "BatchCreateItemModal",
Expand Down Expand Up @@ -414,16 +415,12 @@ export default {
? this.$store.state.sample_list.map((x) => x.item_id)
: [];
},
someValidationMessagePresent() {
return this.itemIDValidationMessages.some();
},
itemIDValidationMessages() {
isValidEntryID() {
return this.items.map((item, index, items) => {
if (item.item_id == null) {
return "";
} // Don't throw an error before the user starts typing

// check that item id isn't repeated in this table
}
// Check if item ID is repeated in the table
if (
items
.slice(0, index)
Expand All @@ -433,25 +430,7 @@ export default {
return "ID is repeated from an above row.";
}

if (
this.takenItemIds.includes(item.item_id) ||
this.takenSampleIds.includes(item.item_id)
) {
return `<a href='edit/${item.item_id}'>${item.item_id}</a> already in use.`;
}
if (!/^[a-zA-Z0-9._-]+$/.test(item.item_id)) {
return "ID can only contain alphanumeric characters, dashes ('-') and underscores ('_') and periods ('.')";
}
if (/^[._-]/.test(item.item_id) | /[._-]$/.test(item.item_id)) {
return "ID cannot start or end with puncutation";
}
if (/\s/.test(item.item_id)) {
return "ID cannot have any spaces";
}
if (item.item_id.length < 1 || item.item_id.length > 40) {
return "ID must be between 1 and 40 characters in length";
}
return "";
return validateEntryID(item.item_id, this.takenItemIds, this.takenSampleIds);
});
},
},
Expand Down
23 changes: 6 additions & 17 deletions webapp/src/components/CollectionSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@search="debouncedAsyncSearch"
>
<template #no-options="{ searching }">
<span v-if="validationMessage" class="form-error">{{ validationMessage }}</span>
<span v-if="isValidEntryID" class="form-error">{{ isValidEntryID }}</span>
<span v-else-if="searching"> Collection already selected </span>
<span v-else class="empty-search"> Search for a collection... </span>
</template>
Expand Down Expand Up @@ -39,6 +39,7 @@
import vSelect from "vue-select";
import FormattedCollectionName from "@/components/FormattedCollectionName.vue";
import { searchCollections, createNewCollection } from "@/server_fetch_utils.js";
import { validateEntryID } from "@/field_utils.js";
import { debounceTime } from "@/resources.js";

export default {
Expand Down Expand Up @@ -82,7 +83,7 @@ export default {
this.searchQuery &&
!this.collections.some((item) => item.collection_id === this.searchQuery) &&
!valueSafe.some((item) => item.collection_id === this.searchQuery) &&
!this.IDValidationMessage()
!this.isValidEntryID
) {
return [
...this.collections,
Expand All @@ -94,8 +95,8 @@ export default {
}
return this.collections;
},
validationMessage() {
return this.IDValidationMessage();
isValidEntryID() {
return validateEntryID(this.searchQuery);
},
},
methods: {
Expand Down Expand Up @@ -126,20 +127,8 @@ export default {
loading(false);
}, debounceTime);
},
IDValidationMessage() {
if (this.searchQuery && !/^[a-zA-Z0-9_-]+$/.test(this.searchQuery)) {
return "ID can only contain alphanumeric characters, dashes ('-'), and underscores ('_').";
}
if (/^[._-]/.test(this.searchQuery) | /[._-]$/.test(this.searchQuery)) {
return "ID cannot start or end with punctuation";
}
if ((this.searchQuery && this.searchQuery.length < 1) || this.searchQuery.length > 40) {
return "ID must be between 1 and 40 characters.";
}
return "";
},
async handleCreateNewCollection() {
if (this.IDValidationMessage()) {
if (this.isValidEntryID) {
return;
} else {
try {
Expand Down
22 changes: 5 additions & 17 deletions webapp/src/components/CreateCollectionModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<form @submit.prevent="submitForm">
<Modal
:model-value="modelValue"
:disable-submit="Boolean(IDValidationMessage) || !Boolean(collection_id)"
:disable-submit="Boolean(isValidEntryID) || !Boolean(collection_id)"
@update:model-value="$emit('update:modelValue', $event)"
>
<template #header> Create new collection </template>
Expand All @@ -19,7 +19,7 @@
required
/>
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="form-error" v-html="IDValidationMessage"></div>
<div class="form-error" v-html="isValidEntryID"></div>
</div>
</div>
<div class="form-row">
Expand Down Expand Up @@ -47,6 +47,7 @@
import Modal from "@/components/Modal.vue";
import ItemSelect from "@/components/ItemSelect.vue";
import { createNewCollection } from "@/server_fetch_utils.js";
import { validateEntryID } from "@/field_utils.js";

export default {
name: "CreateCollectionModal",
Expand All @@ -72,21 +73,8 @@ export default {
? this.$store.state.collection_list.map((x) => x.collection_id)
: [];
},
IDValidationMessage() {
if (this.collection_id == null) {
return "";
} // Don't throw an error before the user starts typing

if (this.takenCollectionIds.includes(this.collection_id)) {
return `<a href='edit/${this.collection_id}'>${this.collection_id}</a> already in use.`;
}
if (!/^[a-zA-Z0-9_-]+$/.test(this.collection_id)) {
return "ID can only contain alphanumeric characters, dashes ('-') and underscores ('_')";
}
if (this.collection_id.length < 1 || this.collection_id.length > 40) {
return "ID must be between 1 and 40 characters in length";
}
return "";
isValidEntryID() {
return validateEntryID(this.collection_id, this.takenCollectionIds);
},
},
methods: {
Expand Down
28 changes: 5 additions & 23 deletions webapp/src/components/CreateEquipmentModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<form class="modal-enclosure" data-testid="create-equipment-form" @submit.prevent="submitForm">
<Modal
:model-value="modelValue"
:disable-submit="Boolean(equipmentIDValidationMessage) || !Boolean(item_id)"
:disable-submit="Boolean(isValidEntryID) || !Boolean(item_id)"
@update:model-value="$emit('update:modelValue', $event)"
>
<template #header> Add equipment </template>
Expand All @@ -13,7 +13,7 @@
<label for="equipment-id" class="col-form-label">ID:</label>
<input id="equipment-id" v-model="item_id" type="text" class="form-control" required />
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="form-error" v-html="equipmentIDValidationMessage"></div>
<div class="form-error" v-html="isValidEntryID"></div>
</div>
<div class="form-group col-md-6">
<label for="create-equipment-modal-item-type-select" class="col-form-label"
Expand Down Expand Up @@ -98,6 +98,7 @@
import Modal from "@/components/Modal.vue";
import ItemSelect from "@/components/ItemSelect.vue";
import { createNewItem } from "@/server_fetch_utils.js";
import { validateEntryID } from "@/field_utils.js";
import { itemTypes } from "@/resources.js";
// import CollectionSelect from "@/components/CollectionSelect.vue";
export default {
Expand Down Expand Up @@ -138,27 +139,8 @@ export default {
? this.$store.state.equipment_list.map((x) => x.item_id)
: [];
},
equipmentIDValidationMessage() {
if (this.item_id == null) {
return "";
} // Don't throw an error before the user starts typing

if (
this.takenItemIds.includes(this.item_id) ||
this.takenEquipmentIds.includes(this.item_id)
) {
return `<a href='edit/${this.item_id}'>${this.item_id}</a> already in use.`;
}
if (!/^[a-zA-Z0-9._-]+$/.test(this.item_id)) {
return "ID can only contain alphanumeric characters, dashes ('-') and underscores ('_') and periods ('.')";
}
if (/^[._-]/.test(this.item_id) | /[._-]$/.test(this.item_id)) {
return "ID cannot start or end with puncutation";
}
if (this.item_id.length < 1 || this.item_id.length > 40) {
return "ID must be between 1 and 40 characters in length";
}
return "";
isValidEntryID() {
return validateEntryID(this.item_id, this.takenItemIds, this.takenEquipmentIds);
},
},
methods: {
Expand Down
27 changes: 5 additions & 22 deletions webapp/src/components/CreateItemModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
<form class="modal-enclosure" data-testid="create-item-form" @submit.prevent="submitForm">
<Modal
:model-value="modelValue"
:disable-submit="
Boolean(itemIDValidationMessage) || (!generateIDAutomatically && !Boolean(item_id))
"
:disable-submit="Boolean(isValidEntryID) || (!generateIDAutomatically && !Boolean(item_id))"
@update:model-value="$emit('update:modelValue', $event)"
>
<template #header> Add new item </template>
Expand All @@ -22,7 +20,7 @@
:required="!generateIDAutomatically"
/>
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="form-error" v-html="itemIDValidationMessage"></div>
<div class="form-error" v-html="isValidEntryID"></div>
<div class="form-check mt-1 ml-1">
<input
id="create-item-auto-id-checkbox"
Expand Down Expand Up @@ -109,6 +107,7 @@
import Modal from "@/components/Modal.vue";
import ItemSelect from "@/components/ItemSelect.vue";
import { createNewItem } from "@/server_fetch_utils.js";
import { validateEntryID } from "@/field_utils.js";
import { itemTypes, SAMPLE_TABLE_TYPES } from "@/resources.js";
import CollectionSelect from "@/components/CollectionSelect.vue";
export default {
Expand Down Expand Up @@ -156,24 +155,8 @@ export default {
? this.$store.state.sample_list.map((x) => x.item_id)
: [];
},
itemIDValidationMessage() {
if (this.item_id == null) {
return "";
} // Don't throw an error before the user starts typing

if (this.takenItemIds.includes(this.item_id) || this.takenSampleIds.includes(this.item_id)) {
return `<a href='edit/${this.item_id}'>${this.item_id}</a> already in use.`;
}
if (!/^[a-zA-Z0-9._-]+$/.test(this.item_id)) {
return "ID can only contain alphanumeric characters, dashes ('-') and underscores ('_') and periods ('.')";
}
if (/^[._-]/.test(this.item_id) | /[._-]$/.test(this.item_id)) {
return "ID cannot start or end with puncutation";
}
if (this.item_id.length < 1 || this.item_id.length > 40) {
return "ID must be between 1 and 40 characters in length";
}
return "";
isValidEntryID() {
return validateEntryID(this.item_id, this.takenItemIds, this.takenSampleIds);
},
},
created() {
Expand Down
43 changes: 43 additions & 0 deletions webapp/src/field_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,46 @@ export function createComputedSetterForCollectionField(collection_field) {
},
};
}

/**
* Validates an ID against various criteria and checks if it's already in use.
*
* @param {string} id - The ID to validate.
* @param {string[]} [takenIds=[]] - Array of already taken item IDs.
* @param {string[]} [existingIds=[]] - Array of IDs already known to exist.
*
* @returns {string} An empty string if the ID is valid and not in use, otherwise an error message.
* If the ID is in use, it returns a string error message with an HTML link to edit the ID.
*
* @example
* // Returns an empty string for a valid ID
* validateEntryID("valid_id_123");
*
* @example
* // Returns an error message for an invalid ID
* validateEntryID("invalid id with spaces");
*
* @example
* // Returns a message with a link if the ID is already in use
* validateEntryID("taken_id", ["taken_id"]);
*/
export function validateEntryID(id, takenIds = [], existingIds = []) {
if (id == null) {
return "";
}

if (takenIds.includes(id) || existingIds.includes(id)) {
return `<a href='edit/${id}'>${id}</a> already in use.`;
}

if (!/^[a-zA-Z0-9_-]+$/.test(id)) {
return "ID can only contain alphanumeric characters, dashes ('-'), and underscores ('_').";
}
if (/^[._-]/.test(id) | /[._-]$/.test(id)) {
return "ID cannot start or end with punctuation";
}
if (id.length < 1 || id.length > 40) {
return "ID must be between 1 and 40 characters.";
}
return "";
}
Loading