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

Saved segments spike #4648

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft

Saved segments spike #4648

wants to merge 1 commit into from

Conversation

apata
Copy link
Contributor

@apata apata commented Oct 2, 2024

This PR outlines schema and API structure for saved segments. Not intended to be merged as is.

Copy link
Contributor

@macobo macobo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial thoughts. 🚀

@@ -491,6 +491,7 @@ defmodule Plausible.Stats.Filters.QueryParserTest do
"metrics" => ["visitors"],
"date_range" => "all",
"filters" => [
["is", "segment", [200]],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it's not clear whether this is intended as the full code: Nit: separate test.

site: site,
user: user
} do
name = "foo"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I don't think DRY-ing the variables here helps here - it just ends up with a wordier test where you need to scroll back-and-forth.

"segment" => %{
"description" => nil,
"name" => ^name,
"segment_data" => ^segment_data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This is confusing to follow - it isn't clear what the response actually looks like. I suggest the following structure.

segment = from(s in segments, where: %{ site_id: ^site_id }) |> Repo.one()

assert json_response(conn, 200) == %{
               "role" => "owner",
               "segment" => %{ ... }
}

And using fully-hard-coded values

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I struggled with the timestamps in this test file. Any tips on how to omit them from the comparison while remaining brief?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question - elixir assertions don't have any clever shorthands like any(datetime) for assert. I'll dig into existing tests for controllers and see how they have solved it. 🤔 Will provide an answer tomorrow!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dug a bit.

We don't have a usecase yet for exposing updated_at or inserted_at at the API level. Looking at similar models like lib/plausible/auth/invitation.ex and lib/plausible/site.ex we don't expose them either. I'd remove exposing them.

I'd rewrite the test as follows:

  describe "POST /internal-api/:domain/segments" do
    setup [:create_user, :create_new_site, :log_in]

    test "creates segment successfully", %{conn: conn, site: site} do
      conn =
        post(conn, "/internal-api/#{site.domain}/segments", %{
          "segment_data" => %{"filters" => [["is", "visit:entry_page", ["/blog"]]]},
          "name" => "Blog entry"
        })

      segment = Plausible.Repo.one(Plausible.Segment)

      assert json_response(conn, 200) == %{
               "role" => "owner",
               "segment" => %{
                 "id" => segment.id,
                 "name" => "Blog entry",
                 "segment_data" => %{"filters" => [["is", "visit:entry_page", ["/blog"]]]},
                 "description" => nil,
               }
             }
    end
  end

lib/plausible/stats/breakdown.ex Outdated Show resolved Hide resolved

@filter_tree_operators [:not, :and, :or]

def parse_filters(filters) when is_list(filters) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't apply the JSON schema which is a problem.

def change do
create table(:segments) do
add :name, :string, null: false
add :segment_data, :map, null: false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not add :segment_filters, :array, null: false or something equivelent? What are we winning by nesting the data under filters and creating an abstraction?

"maxItems": 3,
"items": [
{
"$ref": "#/definitions/filter_operation_for_segments"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's inline the operation given there's only one and we're not repeating the ref anywhere.

Suggested change
"$ref": "#/definitions/filter_operation_for_segments"
"const": "is"


defp validate_segment_data(changeset) do
case get_field(changeset, :segment_data) do
%{"filters" => filters} when is_list(filters) ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should validate here that it's valid and doesn't contain any nested segment references right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed! It should be validated, but at what level? I didn't want to make this module depend on query / filters parsing. How about doing that in the API controller?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing it in the controller seems valid and best (and if it's already validated then sorry for missing it!).

role = "owner"

%{id: segment_id} =
insert(:segment,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than insert it here, would it make sense to use the other APIs to create these to simulate user behavior?

@apata apata force-pushed the saved-segments-spec branch 2 times, most recently from ad1bf06 to 5947e73 Compare October 11, 2024 10:18
@apata apata added the preview label Oct 24, 2024
Copy link

Preview environment👷🏼‍♀️🏗️
PR-4648

@apata apata force-pushed the saved-segments-spec branch 2 times, most recently from 437784e to 7fe1c50 Compare November 5, 2024 12:11
@CLAassistant
Copy link

CLAassistant commented Nov 14, 2024

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants