Skip to content
Open
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
16 changes: 16 additions & 0 deletions server/app/controllers/api/v1/connectors_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,22 @@ def connector_params
:connector_name, :name, :description, :query_type,
configuration: {})
end
<<<<<<< HEAD
=======

def filter_params
{
workspace: current_workspace,
type: params[:type],
category: params[:category],
sub_category: params[:sub_category],
exclude_sub_category: params[:exclude_sub_category],
provider: params[:provider],
page: params[:page],
per_page: params[:per_page]
}
end
>>>>>>> f6d71341a (fix(CE): added an extra filter in connectors api for excluding on basis of sub category (#1893))
end
# rubocop:enable Metrics/ClassLength
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ module ConnectorDefinitions
class FilterConnectorDefinitions
include Interactor

<<<<<<< HEAD
=======
HOSTED_DATA_STORES = {
"AI Squared Vector Store" => "Postgresql",
"AI Squared Database" => "Postgresql"
}.freeze

SUB_CATEGORY_MAP = {
"llm" => Connector::LLM_SUB_CATEGORIES,
"database" => Connector::DATABASE_SUB_CATEGORIES,
"web" => Connector::WEB_SUB_CATEGORIES,
"ai_ml_service" => Connector::AI_ML_SERVICE_SUB_CATEGORIES,
"vector" => Connector::VECTOR_SUB_CATEGORIES
}.freeze

>>>>>>> f6d71341a (fix(CE): added an extra filter in connectors api for excluding on basis of sub category (#1893))
def call
context.connectors = Multiwoven::Integrations::Service.connectors.with_indifferent_access

Expand All @@ -24,6 +40,16 @@ def filter_connectors_by_category
end
context.connectors[:source] = filter_by_category(context.connectors[:source], categories)
context.connectors[:destination] = filter_by_category(context.connectors[:destination], categories)
<<<<<<< HEAD
=======
context.connectors[:destination] = filter_hosted_data_stores(context.connectors[:destination], context.workspace)
end

def filter_connectors_by_sub_category
sub_categories = SUB_CATEGORY_MAP.fetch(context.sub_category, [context.sub_category])
context.connectors[:source] = filter_by_sub_category(context.connectors[:source], sub_categories)
context.connectors[:destination] = filter_by_sub_category(context.connectors[:destination], sub_categories)
>>>>>>> f6d71341a (fix(CE): added an extra filter in connectors api for excluding on basis of sub category (#1893))
end

def filter_by_category(connectors, categories)
Expand Down
62 changes: 62 additions & 0 deletions server/app/interactors/connectors/filter_connectors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

module Connectors
class FilterConnectors
include Interactor

def call
connectors = context.workspace.connectors

connectors = filter_by_type(connectors)
connectors = filter_by_category(connectors)
connectors = filter_by_sub_category(connectors)
connectors = filter_by_exclude_sub_category(connectors)
connectors = filter_by_provider(connectors)
connectors = paginate(connectors)

context.connectors = connectors
end

private

def filter_by_type(connectors)
return connectors if context.type.blank?

connectors.send(context.type.downcase)
end

def filter_by_category(connectors)
return connectors if context.category.blank?

connectors.send(context.category.downcase)
end

def filter_by_exclude_sub_category(connectors)
return connectors if context.exclude_sub_category.blank?

sub_categories = ConnectorDefinitions::FilterConnectorDefinitions::SUB_CATEGORY_MAP.fetch(
context.exclude_sub_category.downcase, [context.exclude_sub_category.downcase]
)
connectors.where.not(connector_sub_category: sub_categories)
end

def filter_by_sub_category(connectors)
return connectors if context.sub_category.blank?

connectors.send(context.sub_category.downcase)
end

def filter_by_provider(connectors)
return connectors if context.provider.blank?

connectors.where(connector_name: context.provider)
end

def paginate(connectors)
page = context.page || 1
per_page = context.per_page

connectors.page(page).per(per_page)
end
end
end
200 changes: 200 additions & 0 deletions server/spec/interactors/connectors/filter_connectors_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Connectors::FilterConnectors do
let(:workspace) { create(:workspace) }

let!(:source_connector) do
create(:connector, workspace:, connector_type: "source", connector_name: "Snowflake")
end

let!(:destination_connector) do
create(:connector, workspace:, connector_type: "destination", connector_name: "Klaviyo")
end

describe "#call" do
context "without any filters" do
it "returns all connectors for the workspace" do
result = described_class.call(workspace:)

expect(result.success?).to be true
expect(result.connectors).to include(source_connector, destination_connector)
end
end

context "with type filter" do
it "returns only source connectors when type is source" do
result = described_class.call(workspace:, type: "source")

expect(result.success?).to be true
expect(result.connectors).to include(source_connector)
expect(result.connectors).not_to include(destination_connector)
end

it "returns only destination connectors when type is destination" do
result = described_class.call(workspace:, type: "destination")

expect(result.success?).to be true
expect(result.connectors).to include(destination_connector)
expect(result.connectors).not_to include(source_connector)
end

it "handles uppercase type filter" do
result = described_class.call(workspace:, type: "SOURCE")

expect(result.success?).to be true
expect(result.connectors).to include(source_connector)
expect(result.connectors).not_to include(destination_connector)
end
end

context "with category filter" do
let!(:ai_ml_connector) do
create(:connector, workspace:, connector_type: "source",
connector_name: "OpenAI", connector_category: "AI Model")
end

let!(:data_connector) do
create(:connector, workspace:, connector_type: "source",
connector_name: "Postgres", connector_category: "Database")
end

it "returns only ai_ml connectors when category is ai_ml" do
result = described_class.call(workspace:, category: "ai_ml")

expect(result.success?).to be true
expect(result.connectors).to include(ai_ml_connector)
expect(result.connectors).not_to include(data_connector)
end

it "returns only data connectors when category is data" do
result = described_class.call(workspace:, category: "data")

expect(result.success?).to be true
expect(result.connectors).to include(data_connector)
expect(result.connectors).not_to include(ai_ml_connector)
end
end

context "with sub_category filter" do
let!(:llm_connector) do
create(:connector, workspace:, connector_type: "source",
connector_name: "OpenAI", connector_sub_category: "LLM")
end

let!(:database_connector) do
create(:connector, workspace:, connector_type: "source",
connector_name: "Postgres", connector_sub_category: "Relational Database")
end

it "returns only llm connectors when sub_category is llm" do
result = described_class.call(workspace:, sub_category: "llm")

expect(result.success?).to be true
expect(result.connectors).to include(llm_connector)
end

it "returns only database connectors when sub_category is database" do
result = described_class.call(workspace:, sub_category: "database")

expect(result.success?).to be true
expect(result.connectors).to include(database_connector)
end
end

context "with exclude_sub_category filter" do
let!(:llm_connector) do
create(:connector, workspace:, connector_type: "source",
connector_name: "OpenAI", connector_sub_category: "LLM")
end

let!(:database_connector) do
create(:connector, workspace:, connector_type: "source",
connector_name: "Postgres", connector_sub_category: "Relational Database")
end

it "excludes database connectors when exclude_sub_category is database" do
result = described_class.call(workspace:, exclude_sub_category: "database")

expect(result.connectors).to include(llm_connector)
expect(result.connectors).not_to include(database_connector)
end

it "excludes llm connectors when exclude_sub_category is llm" do
result = described_class.call(workspace:, exclude_sub_category: "llm")

expect(result.connectors).to include(database_connector)
expect(result.connectors).not_to include(llm_connector)
end

it "returns all connectors when exclude_sub_category is blank" do
result = described_class.call(workspace:, exclude_sub_category: "")

expect(result.connectors).to include(llm_connector, database_connector)
end
end

context "with provider filter" do
it "returns connectors matching the provider name" do
result = described_class.call(workspace:, provider: "Snowflake")

expect(result.success?).to be true
expect(result.connectors).to include(source_connector)
expect(result.connectors).not_to include(destination_connector)
end

it "returns empty when provider does not match any connector" do
result = described_class.call(workspace:, provider: "NonExistent")

expect(result.success?).to be true
expect(result.connectors).to be_empty
end
end

context "with multiple filters" do
let!(:another_source_connector) do
create(:connector, workspace:, connector_type: "source", connector_name: "Postgres")
end

it "applies all filters together" do
result = described_class.call(workspace:, type: "source", provider: "Snowflake")

expect(result.success?).to be true
expect(result.connectors).to include(source_connector)
expect(result.connectors).not_to include(destination_connector)
expect(result.connectors).not_to include(another_source_connector)
end
end

context "with pagination" do
it "paginates results with specified page and per_page" do
result = described_class.call(workspace:, page: 1, per_page: 1)

expect(result.success?).to be true
expect(result.connectors.size).to eq(1)
end

it "returns second page of results" do
result = described_class.call(workspace:, page: 2, per_page: 1)

expect(result.success?).to be true
expect(result.connectors.size).to eq(1)
end

it "defaults to page 1 when page is not provided" do
result = described_class.call(workspace:)

expect(result.success?).to be true
expect(result.connectors).not_to be_empty
end

it "returns empty when page exceeds available results" do
result = described_class.call(workspace:, page: 100, per_page: 10)

expect(result.success?).to be true
expect(result.connectors).to be_empty
end
end
end
end
47 changes: 47 additions & 0 deletions server/spec/requests/api/v1/connectors_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,53 @@
expect(result["data"].count).to eql(0)
end

<<<<<<< HEAD
=======
it "returns connectors filtered by provider with pagination" do
get "/api/v1/connectors?provider=Klaviyo&page=1&per_page=10", headers: auth_headers(user, workspace_id)
expect(response).to have_http_status(:ok)
result = JSON.parse(response.body)
expect(result["data"].count).to eql(1)
expect(result["data"].first.dig("attributes", "connector_name")).to eq("Klaviyo")
expect(result.dig("links", "first")).to include("page=1")
end

it "returns connectors excluding llm sub_category" do
get "/api/v1/connectors?exclude_sub_category=llm", headers: auth_headers(user, workspace_id)
expect(response).to have_http_status(:ok)
result = JSON.parse(response.body)
ids = result["data"].map { |c| c["id"] }
expect(ids).not_to include(llm_connector.id.to_s)
expect(ids).to include(vector_connector.id.to_s)
end

it "returns connectors excluding vector sub_category" do
get "/api/v1/connectors?exclude_sub_category=vector", headers: auth_headers(user, workspace_id)
expect(response).to have_http_status(:ok)
result = JSON.parse(response.body)
ids = result["data"].map { |c| c["id"] }
expect(ids).not_to include(vector_connector.id.to_s)
expect(ids).to include(llm_connector.id.to_s)
end

it "returns connectors excluding ai_ml_service sub_category" do
get "/api/v1/connectors?exclude_sub_category=ai_ml_service", headers: auth_headers(user, workspace_id)
expect(response).to have_http_status(:ok)
result = JSON.parse(response.body)
ids = result["data"].map { |c| c["id"] }
expect(ids).not_to include(ai_ml_service_connector.id.to_s)
end

it "returns source connectors excluding llm sub_category" do
get "/api/v1/connectors?type=source&exclude_sub_category=llm", headers: auth_headers(user, workspace_id)
expect(response).to have_http_status(:ok)
result = JSON.parse(response.body)
ids = result["data"].map { |c| c["id"] }
expect(ids).not_to include(llm_connector.id.to_s)
expect(result["data"].all? { |c| c.dig("attributes", "connector_type") == "source" }).to be true
end

>>>>>>> f6d71341a (fix(CE): added an extra filter in connectors api for excluding on basis of sub category (#1893))
it "returns an error response for connectors" do
get "/api/v1/connectors?type=destination1", headers: auth_headers(user, workspace_id)
expect(response).to have_http_status(:bad_request)
Expand Down
Loading