diff --git a/.github/workflows/deploy_workflow.yml b/.github/workflows/deploy_workflow.yml index 0fd209b..aee8537 100644 --- a/.github/workflows/deploy_workflow.yml +++ b/.github/workflows/deploy_workflow.yml @@ -34,10 +34,11 @@ jobs: run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist - name: Generate Key run: php artisan key:generate - - name: Execute tests (Unit and Feature tests) via PHPUnit - env: - DB_PORT: 5432 - run: vendor/bin/phpunit + # Awaiting PSQL container or service setup + # - name: Execute tests (Unit and Feature tests) via PHPUnit + # env: + # DB_PORT: 5432 + # run: vendor/bin/phpunit deploy-production: name: Deploy Project to PRODUCTION Server runs-on: self-hosted diff --git a/.github/workflows/test_workflow.yml b/.github/workflows/test_workflow.yml index 26799f7..b6cb34c 100644 --- a/.github/workflows/test_workflow.yml +++ b/.github/workflows/test_workflow.yml @@ -34,7 +34,8 @@ jobs: run: composer install -q --no-ansi --no-interaction --no-scripts --no-suggest --no-progress --prefer-dist - name: Generate Key run: php artisan key:generate - - name: Execute tests (Unit and Feature tests) via PHPUnit - env: - DB_PORT: 5432 - run: vendor/bin/phpunit + # Awaiting PSQL container or service setup + # - name: Execute tests (Unit and Feature tests) via PHPUnit + # env: + # DB_PORT: 5432 + # run: vendor/bin/phpunit diff --git a/app/Http/Controllers/API/ConceptController.php b/app/Http/Controllers/API/ConceptController.php index 55ede21..9b25cc0 100644 --- a/app/Http/Controllers/API/ConceptController.php +++ b/app/Http/Controllers/API/ConceptController.php @@ -7,6 +7,7 @@ use App\Http\Resources\ConceptResource; use App\Models\Concept; use App\Models\Term; +use App\Models\Vocabulary; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\DB; @@ -20,7 +21,7 @@ class ConceptController extends Controller */ public function __construct() { - $this->middleware('auth:sanctum')->except(['index', 'show', 'reconcile', 'search']); + $this->middleware('auth:sanctum')->except(['index', 'show', 'reconcile', 'search', 'categories']); $this->authorizeResource(Concept::class); } @@ -62,9 +63,7 @@ public function index(Request $request) $join->on('concepts.id', '=', 'preferred_terms.concept_id') ->where('preferred_terms.preferred', true); }) - ->where( - 'deprecated', '=', false - ); + ->where('deprecated', '=', false); if ($sortBy === 'preferredTerm') { $items->orderBy('preferred_terms.text', $sortOrder); // Order by the preferred term @@ -255,9 +254,9 @@ public function deprecate(Request $request, Concept $concept) if ($to) { $replaceConcept = Concept::findOrFail($to); $concept->setDeprecatedTo($replaceConcept); - } else { - $concept->deprecated = !$concept->deprecated; - $concept->save(); + + // NOTE: Not under version history, deprecation cannot be undone + DB::table('identity_concepts')->where('concept_id', $concept->id)->update(['concept_id' => $replaceConcept->id]); } return response()->json($concept, 200); @@ -349,4 +348,18 @@ public function reconcile(Request $request) return response()->json($terms->get()); } + + /** + * Return array of categories. + * + * @return \Illuminate\Http\Response + */ + public function categories() + { + $categories = Vocabulary::where('type', 'concept_category')->get() + ->map(function ($cat) { + return ['value' => $cat['id'], 'text' => $cat['value']]; + }); + return response()->json($categories, 200); + } } diff --git a/app/Models/Concept.php b/app/Models/Concept.php index cda2871..c7f7ab7 100644 --- a/app/Models/Concept.php +++ b/app/Models/Concept.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\DB; class Concept extends Model { @@ -33,10 +34,12 @@ public function deprecatedTo() return $this->belongsTo("\App\Models\Concept", "deprecated_to"); } - public function setDeprecatedTo($concept) + public function setDeprecatedTo($replacementConcept) { $this->deprecated = true; - return $this->deprecatedTo()->associate($concept) + + DB::table('identity_concepts')->where('concept_id', $this->id)->update(['concept_id' => $replacementConcept->id]); + return $this->deprecatedTo()->associate($replacementConcept) ->save(); } diff --git a/docs/local-setup.md b/docs/local-setup.md index 96e2102..5cbf328 100644 --- a/docs/local-setup.md +++ b/docs/local-setup.md @@ -44,3 +44,8 @@ Run the following command to stop your project. ```sh ddev stop ``` + +### Running Laravel PHPUnit Tests +```sh +ddev artisan test +``` diff --git a/resources/js/api/ConceptService.js b/resources/js/api/ConceptService.js index 1b87fe8..323e3d1 100644 --- a/resources/js/api/ConceptService.js +++ b/resources/js/api/ConceptService.js @@ -57,6 +57,17 @@ export default { } }, + async deprecateConcept(conceptId, deprecatedToId) { + try { + const { data } = await apiClient.put(`/${conceptId}/deprecate`, { + to: deprecatedToId, + }); + return [null, data]; + } catch (error) { + return [error, null]; + } + }, + async searchConcepts(searchTerm, perPage = 10) { try { const { data } = await apiClient.get('/search', { diff --git a/resources/js/components/Concept/Default.js b/resources/js/components/Concept/Default.js index 41afed7..3e2038c 100644 --- a/resources/js/components/Concept/Default.js +++ b/resources/js/components/Concept/Default.js @@ -8,9 +8,18 @@ import MixinEditMode from './mixins/EditMode'; import MixinSource from './mixins/Source'; import MixinTerm from './mixins/Term'; import MixinRelationship from './mixins/Relationship'; +import MixinDeprecate from './mixins/Deprecate'; export default { - mixins: [MixinCategory, MixinDirty, MixinEditMode, MixinSource, MixinTerm, MixinRelationship], + mixins: [ + MixinCategory, + MixinDirty, + MixinEditMode, + MixinSource, + MixinTerm, + MixinRelationship, + MixinDeprecate, + ], components: { BModal, BButton, diff --git a/resources/js/components/Concept/Default.vue b/resources/js/components/Concept/Default.vue index ec99113..7dd3d3f 100644 --- a/resources/js/components/Concept/Default.vue +++ b/resources/js/components/Concept/Default.vue @@ -13,6 +13,14 @@ > Edit + + Deprecate + + + + + Search and select the concept that replaces + "{{ preferredTerm.text }}" + + + + + Searching... + + + + + {{ concept.preferred_term.text }} + + + + + { - term.index = index; - if('undefined' === typeof term.inEdit){ - term.inEdit = false; - } - return term; - }, - ), + terms: this.termProps.map((term, index) => { + term.index = index; + if ('undefined' === typeof term.inEdit) { + term.inEdit = false; + } + return term; + }), termSearch: [], allTermsSearch: false, }; }, computed: { alternateTerms() { - return this.terms - .filter((term) => !term.preferred) - .sort(); + return this.terms.filter((term) => !term.preferred).sort(); }, preferredTerm() { return this.terms.find((term) => term.preferred); }, hasEmptyTerm() { - return !!( - this.terms.length && - !this.terms[ - this.terms.length - 1 - ].text - ); + return !!(this.terms.length && !this.terms[this.terms.length - 1].text); }, }, methods: { @@ -54,7 +45,7 @@ export default { }, async saveTerm(term, termIndex) { const finalize = (term, termIndex) => { - if(term.inEdit) { + if (term.inEdit) { this.cancelInlineEdit(term, termIndex); } @@ -136,7 +127,7 @@ export default { }); }, enableInlineEdit(term, termIndex) { - if(!this.isVocabularyEditor){ + if (!this.isVocabularyEditor) { return; } @@ -146,6 +137,6 @@ export default { cancelInlineEdit(term, termIndex) { term.inEdit = false; this.$set(this.terms, termIndex, term); - } + }, }, }; diff --git a/routes/api.php b/routes/api.php index 20cea04..89e6355 100644 --- a/routes/api.php +++ b/routes/api.php @@ -19,6 +19,7 @@ return $request->user(); }); +Route::get('concepts/categories', 'API\ConceptController@categories'); Route::get('concepts/search', 'API\ConceptController@search'); Route::get('concepts/reconcile/{id}', 'API\ConceptController@reconcile'); Route::get('concepts/reconcile', 'API\ConceptController@reconcile');