diff --git a/lib/workos.rb b/lib/workos.rb index b094f4fb..12a5617c 100644 --- a/lib/workos.rb +++ b/lib/workos.rb @@ -59,6 +59,7 @@ def self.key autoload :Event, 'workos/event' autoload :Events, 'workos/events' autoload :Factor, 'workos/factor' + autoload :FeatureFlag, 'workos/feature_flag' autoload :Impersonator, 'workos/impersonator' autoload :Invitation, 'workos/invitation' autoload :MagicAuth, 'workos/magic_auth' diff --git a/lib/workos/feature_flag.rb b/lib/workos/feature_flag.rb new file mode 100644 index 00000000..85d958e2 --- /dev/null +++ b/lib/workos/feature_flag.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module WorkOS + # The FeatureFlag class provides a lightweight wrapper around + # a WorkOS Feature Flag resource. This class is not meant to be instantiated + # in user space, and is instantiated internally but exposed. + class FeatureFlag + include HashProvider + + attr_accessor :id, :name, :slug, :description, :created_at, :updated_at + + def initialize(json) + hash = JSON.parse(json, symbolize_names: true) + + @id = hash[:id] + @name = hash[:name] + @slug = hash[:slug] + @description = hash[:description] + @created_at = hash[:created_at] + @updated_at = hash[:updated_at] + end + + def to_json(*) + { + id: id, + name: name, + slug: slug, + description: description, + created_at: created_at, + updated_at: updated_at, + } + end + end +end diff --git a/lib/workos/organizations.rb b/lib/workos/organizations.rb index ff83ca3d..d130fa03 100644 --- a/lib/workos/organizations.rb +++ b/lib/workos/organizations.rb @@ -224,6 +224,46 @@ def list_organization_roles(organization_id:) ) end + # Retrieve a list of feature flags for the given organization. + # + # @param [String] organization_id The ID of the organization to fetch feature flags for. + # @param [Hash] options + # @option options [String] before A pagination argument used to request + # feature flags before the provided FeatureFlag ID. + # @option options [String] after A pagination argument used to request + # feature flags after the provided FeatureFlag ID. + # @option options [Integer] limit A pagination argument used to limit the number + # of listed FeatureFlags that are returned. + # @option options [String] order The order in which to paginate records + # + # @example + # WorkOS::Organizations.list_organization_feature_flags(organization_id: 'org_01EHZNVPK3SFK441A1RGBFSHRT') + # => #] ...> + # + # @return [WorkOS::Types::ListStruct] - Collection of FeatureFlag objects + def list_organization_feature_flags(organization_id:, options: {}) + options[:order] ||= 'desc' + response = execute_request( + request: get_request( + path: "/organizations/#{organization_id}/feature-flags", + auth: true, + params: options, + ), + ) + + parsed_response = JSON.parse(response.body) + + feature_flags = parsed_response['data'].map do |feature_flag| + WorkOS::FeatureFlag.new(feature_flag.to_json) + end + + WorkOS::Types::ListStruct.new( + data: feature_flags, + list_metadata: parsed_response['list_metadata']&.transform_keys(&:to_sym), + ) + end + private def check_and_raise_organization_error(response:) diff --git a/spec/lib/workos/organizations_spec.rb b/spec/lib/workos/organizations_spec.rb index 91394493..5ce146d1 100644 --- a/spec/lib/workos/organizations_spec.rb +++ b/spec/lib/workos/organizations_spec.rb @@ -450,4 +450,120 @@ end end end + + describe '.list_organization_feature_flags' do + context 'with no options' do + it 'returns feature flags for organization' do + expected_metadata = { + after: nil, + before: nil, + } + + VCR.use_cassette 'organization/list_organization_feature_flags' do + feature_flags = described_class.list_organization_feature_flags( + organization_id: 'org_01HX7Q7R12H1JMAKN75SH2G529', + ) + + expect(feature_flags.data.size).to eq(2) + expect(feature_flags.list_metadata).to eq(expected_metadata) + end + end + end + + context 'with the before option' do + it 'forms the proper request to the API' do + request_args = [ + '/organizations/org_01HX7Q7R12H1JMAKN75SH2G529/feature-flags?before=before-id&'\ + 'order=desc', + 'Content-Type' => 'application/json' + ] + + expected_request = Net::HTTP::Get.new(*request_args) + + expect(Net::HTTP::Get).to receive(:new).with(*request_args). + and_return(expected_request) + + VCR.use_cassette 'organization/list_organization_feature_flags', match_requests_on: [:path] do + feature_flags = described_class.list_organization_feature_flags( + organization_id: 'org_01HX7Q7R12H1JMAKN75SH2G529', + options: { before: 'before-id' }, + ) + + expect(feature_flags.data.size).to eq(2) + end + end + end + + context 'with the after option' do + it 'forms the proper request to the API' do + request_args = [ + '/organizations/org_01HX7Q7R12H1JMAKN75SH2G529/feature-flags?after=after-id&'\ + 'order=desc', + 'Content-Type' => 'application/json' + ] + + expected_request = Net::HTTP::Get.new(*request_args) + + expect(Net::HTTP::Get).to receive(:new).with(*request_args). + and_return(expected_request) + + VCR.use_cassette 'organization/list_organization_feature_flags', match_requests_on: [:path] do + feature_flags = described_class.list_organization_feature_flags( + organization_id: 'org_01HX7Q7R12H1JMAKN75SH2G529', + options: { after: 'after-id' }, + ) + + expect(feature_flags.data.size).to eq(2) + end + end + end + + context 'with the limit option' do + it 'forms the proper request to the API' do + request_args = [ + '/organizations/org_01HX7Q7R12H1JMAKN75SH2G529/feature-flags?limit=10&'\ + 'order=desc', + 'Content-Type' => 'application/json' + ] + + expected_request = Net::HTTP::Get.new(*request_args) + + expect(Net::HTTP::Get).to receive(:new).with(*request_args). + and_return(expected_request) + + VCR.use_cassette 'organization/list_organization_feature_flags', match_requests_on: [:path] do + feature_flags = described_class.list_organization_feature_flags( + organization_id: 'org_01HX7Q7R12H1JMAKN75SH2G529', + options: { limit: 10 }, + ) + + expect(feature_flags.data.size).to eq(2) + end + end + end + + context 'with multiple pagination options' do + it 'forms the proper request to the API' do + request_args = [ + '/organizations/org_01HX7Q7R12H1JMAKN75SH2G529/feature-flags?after=after-id&'\ + 'limit=5&order=asc', + 'Content-Type' => 'application/json' + ] + + expected_request = Net::HTTP::Get.new(*request_args) + + expect(Net::HTTP::Get).to receive(:new).with(*request_args). + and_return(expected_request) + + VCR.use_cassette 'organization/list_organization_feature_flags', match_requests_on: [:path] do + feature_flags = described_class.list_organization_feature_flags( + organization_id: 'org_01HX7Q7R12H1JMAKN75SH2G529', + options: { after: 'after-id', limit: 5, order: 'asc' }, + ) + + expect(feature_flags.data.size).to eq(2) + end + end + end + end end diff --git a/spec/support/fixtures/vcr_cassettes/organization/list_organization_feature_flags.yml b/spec/support/fixtures/vcr_cassettes/organization/list_organization_feature_flags.yml new file mode 100644 index 00000000..3907287f --- /dev/null +++ b/spec/support/fixtures/vcr_cassettes/organization/list_organization_feature_flags.yml @@ -0,0 +1,78 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.workos.com/organizations/org_01HX7Q7R12H1JMAKN75SH2G529/feature-flags?order=desc + body: + encoding: US-ASCII + string: '' + headers: + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - WorkOS; ruby/3.1.4; arm64-darwin24; v5.24.0 + Authorization: + - Bearer + response: + status: + code: 200 + message: OK + headers: + Date: + - Mon, 04 Aug 2025 18:37:04 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Connection: + - keep-alive + Cf-Ray: + - 96a029f4191e2f57-LAX + Cf-Cache-Status: + - DYNAMIC + Etag: + - W/"1f7-g5rU0vT2OhGT9sAPsywR3YS8ePw" + Strict-Transport-Security: + - max-age=15552000; includeSubDomains + Vary: + - Origin, Accept-Encoding + Access-Control-Allow-Credentials: + - 'true' + Content-Security-Policy: + - 'default-src ''self'';base-uri ''self'';block-all-mixed-content;font-src ''self'' + https: data:;frame-ancestors ''self'';img-src ''self'' data:;object-src ''none'';script-src + ''self'';script-src-attr ''none'';style-src ''self'' https: ''unsafe-inline'';upgrade-insecure-requests' + Expect-Ct: + - max-age=0 + Referrer-Policy: + - no-referrer + X-Content-Type-Options: + - nosniff + X-Dns-Prefetch-Control: + - 'off' + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Request-Id: + - 9cd52998-5106-40cf-9080-82c07528c672 + X-Xss-Protection: + - '0' + Set-Cookie: + - _cfuvid=_9dbE_0fDVZ45WXvgEp8frEFOIlVDyARPbMk3AffOcs-1754332624329-0.0.1.1-604800000; + path=/; domain=.workos.com; HttpOnly; Secure; SameSite=None + Server: + - cloudflare + body: + encoding: ASCII-8BIT + string: '{"object":"list","data":[{"object":"feature_flag","id":"flag_01K1V04FV94RNDSN5GKSZVQMYN","slug":"new-sidebar-layout","name":"New + Sidebar Layout","description":"","created_at":"2025-08-04T16:55:15.557Z","updated_at":"2025-08-04T16:55:15.557Z"},{"object":"feature_flag","id":"flag_01K1V02KNS6WHYXKG0DWB87THK","slug":"dark-mode-toggle","name":"Dark + Mode Toggle","description":"","created_at":"2025-08-04T16:54:13.942Z","updated_at":"2025-08-04T16:54:13.942Z"}],"list_metadata":{"before":null,"after":null}}' + recorded_at: Mon, 04 Aug 2025 18:37:04 GMT +recorded_with: VCR 6.3.1