-
Notifications
You must be signed in to change notification settings - Fork 34
Y24-220 Create v2 end point for tagsets #4732
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
Changes from all commits
9d681ed
6ddec84
4f89622
1901b4c
f851c3b
228877a
71fec17
5430b8f
d7f87f6
cefff16
f6d83b7
c808a55
c8b2078
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# frozen_string_literal: true | ||
|
||
module Api | ||
module V2 | ||
# Provides a JSON API controller for TagSet | ||
# See: http://jsonapi-resources.com/ for JSONAPI::Resource documentation | ||
class TagSetsController < JSONAPI::ResourceController | ||
# By default JSONAPI::ResourceController provides most the standard | ||
# behaviour, and in many cases this file may be left empty. | ||
before_action :check_feature_flag | ||
|
||
private | ||
|
||
def check_feature_flag | ||
render json: { error: 'TagSets API is disabled' } unless Flipper.enabled?(:y24_220_enable_tag_set_api) | ||
end | ||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# frozen_string_literal: true | ||
|
||
module Api | ||
module V2 | ||
# Provides a JSON:API representation of {TagSet}, which links together two related tag groups. | ||
# A TagSet represents a logical grouping of tags used for indexing in sequencing experiments. | ||
# It typically consists of a primary tag group and an optional secondary tag group, | ||
# enabling support for both single and dual indexing workflows. | ||
# This resource allows clients to query, filter, and retrieve information about tag sets, | ||
# including their associated tag groups and metadata, through the `/api/v2/tag_sets/` endpoint. | ||
# | ||
# @note Access this resource via the `/api/v2/tag_sets/` endpoint. | ||
# | ||
# @example GET request for all TagSet resources | ||
# GET /api/v2/tag_sets/ | ||
# | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @harrietc52 , I've updated the documentation as well as the Sequencescape API v2.postman_collection.json in smb://files-smb/pipeline_solutions. Please check if it is ok. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Postman looks good, thank you |
||
# @example GET request for a specific TagSet by ID | ||
# GET /api/v2/tag_sets/123/ | ||
# | ||
# @example Filtering by tag group adapter type name | ||
# GET /api/v2/tag_sets/?filter[tag_group_adapter_type_name]=AdapterType1 | ||
# | ||
# @example Filtering by visibility (applied by default) | ||
# GET /api/v2/tag_sets/?filter[visible]=true | ||
# | ||
# For more information about JSON:API, see the [JSON:API Specifications](https://jsonapi.org/format/) | ||
# or the [JSONAPI::Resources](http://jsonapi-resources.com/) package, which implements JSON:API for Sequencescape. | ||
class TagSetResource < BaseResource | ||
immutable | ||
|
||
default_includes :uuid_object, :tag_group, :tag2_group | ||
|
||
### | ||
# Relationships | ||
### | ||
|
||
# @!attribute [r] tag_group | ||
# A relationship for the primary tag group associated with the tag layout template. | ||
# @return [Api::V2::TagGroupResource] | ||
has_one :tag_group, readonly: true | ||
|
||
# @!attribute [r] tag2_group | ||
# A relationship for the secondary tag group associated with the tag layout template. | ||
# This is used during dual indexing, but will not be found during single indexing. | ||
# @return [Api::V2::TagGroupResource] | ||
has_one :tag2_group, readonly: true | ||
|
||
### | ||
# Attributes | ||
### | ||
|
||
# @!attribute [r] uuid | ||
# The UUID of the tag set. | ||
# @return [String]. | ||
attribute :uuid, readonly: true | ||
|
||
# @!attribute [r] name | ||
# The display name of the tag set. | ||
# @return [String] | ||
attribute :name, readonly: true | ||
|
||
### | ||
# Filters | ||
### | ||
|
||
# Allows filtering by the adapter type name of the tag group. | ||
# @example | ||
# GET /api/v2/tag_sets/?filter[tag_group_adapter_type_name]=AdapterType1 | ||
filter :tag_group_adapter_type_name, apply: ->(records, value, _options) { records.by_adapter_type(value) } | ||
|
||
# Allows filtering by visibility. | ||
# @example | ||
# GET /api/v2/tag_sets/?filter[visible]=true | ||
filter :visible, default: true, apply: ->(records, _value, _options) { records.visible } | ||
end | ||
end | ||
end | ||
harrietc52 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rails_helper' | ||
require './spec/requests/api/v2/shared_examples/api_key_authenticatable' | ||
require './spec/requests/api/v2/shared_examples/requests' | ||
|
||
describe 'TagSets API', with: :api_v2 do | ||
let(:model_class) { TagSet } | ||
let(:base_endpoint) { '/api/v2/tag_sets' } | ||
let(:resource_type) { model_class.name.demodulize.pluralize.underscore } | ||
|
||
before { Flipper.enable(:y24_220_enable_tag_set_api) } | ||
|
||
it_behaves_like 'ApiKeyAuthenticatable' | ||
|
||
context 'with multiple TagSets' do | ||
before do | ||
create_list(:tag_set, 5) | ||
create(:tag_set, tag_group: create(:tag_group, visible: false), tag2_group: create(:tag_group)) | ||
api_get base_endpoint | ||
end | ||
|
||
it 'responds with a success http code when sending a list of tag_sets with visible tag groups' do | ||
expect(response).to have_http_status(:success) | ||
end | ||
|
||
it 'returns the correct number of tag_sets with visible tag groups' do | ||
expect(json['data'].length).to eq(5) | ||
end | ||
end | ||
|
||
context 'when filtering tag sets by tag_group_adapter_type_name' do | ||
before do | ||
adapter_type = build(:adapter_type, name: 'adapter_type_1') | ||
create(:tag_set, tag_group: create(:tag_group, adapter_type:), tag2_group: create(:tag_group, adapter_type:)) | ||
create_list(:tag_set, 5) | ||
api_get "#{base_endpoint}?filter[tag_group_adapter_type_name]=adapter_type_1" | ||
end | ||
|
||
it 'responds with a success http code when filtering by tag_group_adapter_type_name' do | ||
expect(response).to have_http_status(:success) | ||
end | ||
|
||
it 'returns the correct number of tag_sets when filtering by tag_group_adapter_type_name' do | ||
expect(json['data'].length).to eq(1) | ||
end | ||
|
||
it 'returns the correct tag_set when filtering by tag_group_adapter_type_name' do | ||
expect(json['data'][0]['attributes']['name']).to eq(TagSet.first.name) | ||
end | ||
end | ||
|
||
context 'with a single resource' do | ||
let(:resource) { create(:tag_set) } | ||
|
||
context 'without included relationships' do | ||
before { api_get "#{base_endpoint}/#{resource.id}" } | ||
|
||
it 'responds with a success http code' do | ||
expect(response).to have_http_status(:success) | ||
end | ||
|
||
it 'returns the correct resource id' do | ||
expect(json.dig('data', 'id')).to eq(resource.id.to_s) | ||
end | ||
|
||
it 'returns the correct resource type' do | ||
expect(json.dig('data', 'type')).to eq(resource_type) | ||
end | ||
|
||
it 'returns the correct resource name' do | ||
expect(json.dig('data', 'attributes', 'name')).to eq(resource.name) | ||
end | ||
|
||
it 'returns the correct resource uuid' do | ||
expect(json.dig('data', 'attributes', 'uuid')).to eq(resource.uuid) | ||
end | ||
|
||
it 'returns reference to tag_group' do | ||
expect(json.dig('data', 'relationships', 'tag_group')).to be_present | ||
end | ||
|
||
it 'returns reference to tag2_group' do | ||
expect(json.dig('data', 'relationships', 'tag2_group')).to be_present | ||
end | ||
end | ||
end | ||
|
||
context 'with included relationships' do | ||
let(:resource) { create(:tag_set) } | ||
|
||
it_behaves_like 'a GET request including a has_one relationship', 'tag_group' | ||
it_behaves_like 'a GET request including a has_one relationship', 'tag2_group' | ||
end | ||
|
||
context 'with included tags for tag groups' do | ||
let(:resource) { create(:tag_set, tag_group:, tag2_group:) } | ||
let(:tag_group) { create(:tag_group, tags:) } | ||
let(:tag2_group) { create(:tag_group, tags: tags2) } | ||
let(:tags) do | ||
[ | ||
build(:tag, oligo: 'AAA', map_id: 1), | ||
build(:tag, oligo: 'TTT', map_id: 2), | ||
build(:tag, oligo: 'CCC', map_id: 3), | ||
build(:tag, oligo: 'GGG', map_id: 4) | ||
] | ||
end | ||
let(:tags2) do | ||
[ | ||
build(:tag, oligo: 'TTT', map_id: 1), | ||
build(:tag, oligo: 'AAA', map_id: 2), | ||
build(:tag, oligo: 'CCC', map_id: 3), | ||
build(:tag, oligo: 'GGG', map_id: 4) | ||
] | ||
end | ||
|
||
before { api_get "#{base_endpoint}/#{resource.id}?include=tag_group,tag2_group" } | ||
|
||
it 'returns a success http code' do | ||
expect(response).to have_http_status(:success) | ||
end | ||
|
||
it 'returns tags for the first tag group within the tag set in included' do | ||
expect(json['included'][0]['attributes']['tags']).to eq( | ||
[ | ||
{ 'index' => 1, 'oligo' => 'AAA' }, | ||
{ 'index' => 2, 'oligo' => 'TTT' }, | ||
{ 'index' => 3, 'oligo' => 'CCC' }, | ||
{ 'index' => 4, 'oligo' => 'GGG' } | ||
] | ||
) | ||
end | ||
|
||
it 'returns tags for the second tag group within the tag set in included' do | ||
expect(json['included'][1]['attributes']['tags']).to eq( | ||
[ | ||
{ 'index' => 1, 'oligo' => 'TTT' }, | ||
{ 'index' => 2, 'oligo' => 'AAA' }, | ||
{ 'index' => 3, 'oligo' => 'CCC' }, | ||
{ 'index' => 4, 'oligo' => 'GGG' } | ||
] | ||
) | ||
end | ||
end | ||
end | ||
harrietc52 marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# frozen_string_literal: true | ||
|
||
require 'rails_helper' | ||
require './app/resources/api/v2/tag_group_resource' | ||
|
||
RSpec.describe Api::V2::TagSetResource, type: :resource do | ||
subject(:resource) { described_class.new(resource_model, {}) } | ||
|
||
let(:resource_model) { build_stubbed(:tag_set, tag_group:, tag2_group:) } | ||
let(:tag_group) { create(:tag_group) } | ||
let(:tag2_group) { create(:tag_group) } | ||
|
||
it { is_expected.to have_model_name 'TagSet' } | ||
|
||
# Test attributes | ||
it 'exposes attributes', :aggregate_failures do | ||
expect(resource).to have_attribute :uuid | ||
expect(resource).to have_attribute :name | ||
expect(resource).not_to have_updatable_field(:id) | ||
expect(resource).not_to have_updatable_field(:uuid) | ||
expect(resource).not_to have_updatable_field(:name) | ||
end | ||
|
||
# Relationships | ||
it { is_expected.to have_a_readonly_has_one(:tag_group).with_class_name('TagGroup') } | ||
it { is_expected.to have_a_readonly_has_one(:tag2_group).with_class_name('TagGroup') } | ||
|
||
describe '#tag group' do | ||
it 'returns the correct tag group information' do | ||
expect(resource.tag_group._model).to eq(tag_group) | ||
end | ||
|
||
it 'returns the correct tag2 group information' do | ||
expect(resource.tag2_group._model).to eq(tag2_group) | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think there should there be an addition to
feature_flags.yml
in this PR?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if you haven't already, would you mind creating a story and a pull request to remove the feature flag, so it's easy to remove when we decide to? There's a team feature flags guidance page on Confluence in case if you haven't seen it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done 👍 #4766
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you 🙏
I think the idea was to go even further, and create the (draft) pull request ready to merge in whenever appropriate - so that removing the feature flag is really effortless (obviously extra effort for you upfront, but hopefully less overall since it's fresh in your head).
Also, thinking about when to remove the flag - on the story you've written "once the prerequisite work (Y25-185) is completed".
We can definitely switch the new feature 'on' once Y25-185 is completed, but I think we could let the flag hang around for a couple of months or so afterwards, just to ensure the page gets used and we haven't missed anything?
This was what I thought about the level of risk (copied from Slack):
Since you've implemented the flag, I feel we may as well keep it around for a little while in case we get any production issues due to the above?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, okay. I agree, and all of your comments make complete sense. I will create a draft PR and chang the timing to remove the flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@KatyTaylor
Following draft PRs created which enable the removal of
SS - #4770
Limber - sanger/limber#2275
Integration suite - https://gitlab.internal.sanger.ac.uk/psd/integration-suite/-/merge_requests/232