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

Add build info query #221

Merged
merged 1 commit into from
Jan 26, 2023
Merged
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
25 changes: 19 additions & 6 deletions lib/console/graphql/build.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ defmodule Console.GraphQl.Build do
timestamps()
end

object :build_info do
field :all, :integer
field :failed, :integer
field :queued, :integer
field :running, :integer
field :successful, :integer
end

delta :build
delta :command

Expand All @@ -81,10 +89,15 @@ defmodule Console.GraphQl.Build do

field :build, :build do
middleware Authenticated

arg :id, non_null(:id)

resolve safe_resolver(&Build.resolve_build/2)
safe_resolve &Build.resolve_build/2
end

field :build_info, :build_info do
middleware Authenticated

safe_resolve &Build.info/2
end
end

Expand All @@ -95,31 +108,31 @@ defmodule Console.GraphQl.Build do
arg :attributes, non_null(:build_attributes)

middleware Rbac, perm: :deploy, arg: [:attributes, :repository]
resolve safe_resolver(&Build.create_build/2)
safe_resolve &Build.create_build/2
end

field :restart_build, :build do
middleware Authenticated
middleware RequiresGit
arg :id, non_null(:id)

resolve safe_resolver(&Build.restart_build/2)
safe_resolve &Build.restart_build/2
end

field :cancel_build, :build do
middleware Authenticated
middleware RequiresGit
arg :id, non_null(:id)

resolve safe_resolver(&Build.cancel_build/2)
safe_resolve &Build.cancel_build/2
end

field :approve_build, :build do
middleware Authenticated
middleware RequiresGit
arg :id, non_null(:id)

resolve safe_resolver(&Build.approve_build/2)
safe_resolve &Build.approve_build/2
end
end

Expand Down
7 changes: 7 additions & 0 deletions lib/console/graphql/resolvers/build.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ defmodule Console.GraphQl.Resolvers.Build do
|> paginate(args)
end

def info(_, %{context: %{current_user: user}}) do
Build.inserted_after(user.build_timestamp)
|> Build.info()
|> Console.Repo.one()
|> ok()
end

def create_build(%{attributes: attrs}, %{context: %{current_user: user}}),
do: Builds.create(attrs, user)

Expand Down
3 changes: 3 additions & 0 deletions lib/console/graphql/resolvers/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ defmodule Console.GraphQl.Resolvers.User do
def read_notifications(_, %{context: %{current_user: user}}),
do: Users.mark_read(user)

def mark_read(args, %{context: %{current_user: user}}),
do: Users.mark_read(user, Map.get(args, :type, :notification))

def resolve_role(%{id: role_id}, _), do: {:ok, Users.get_role!(role_id)}

def resolve_invite(%{id: secure_id}, _),
Expand Down
29 changes: 21 additions & 8 deletions lib/console/graphql/users.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ defmodule Console.GraphQl.Users do
ecto_enum :severity, Severity
ecto_enum :notification_status, Status

enum :read_type do
value :notification
value :build
end

input_object :user_attributes do
field :name, :string
field :email, :string
Expand Down Expand Up @@ -43,14 +48,15 @@ defmodule Console.GraphQl.Users do
end

object :user do
field :id, non_null(:id)
field :name, non_null(:string)
field :email, non_null(:string)
field :deleted_at, :datetime
field :profile, :string
field :roles, :user_roles
field :read_timestamp, :datetime
field :bound_roles, list_of(:role), resolve: fn user, _, _ ->
field :id, non_null(:id)
field :name, non_null(:string)
field :email, non_null(:string)
field :deleted_at, :datetime
field :profile, :string
field :roles, :user_roles
field :read_timestamp, :datetime
field :build_timestamp, :datetime
field :bound_roles, list_of(:role), resolve: fn user, _, _ ->
{:ok, Console.Schema.User.roles(user)}
end

Expand Down Expand Up @@ -259,6 +265,13 @@ defmodule Console.GraphQl.Users do
safe_resolve &User.update_user/2
end

field :mark_read, :user do
middleware Authenticated
arg :type, :read_type

safe_resolve &User.mark_read/2
end

field :create_group, :group do
middleware Authenticated
middleware AdminRequired
Expand Down
18 changes: 18 additions & 0 deletions lib/console/schema/build.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,24 @@ defmodule Console.Schema.Build do
from(b in query, limit: 1)
end

@frag "case when ? = ? then 1 else 0 end"

def inserted_after(query \\ __MODULE__, ts)
def inserted_after(query, nil), do: query
def inserted_after(query, ts), do: from(b in query, where: b.inserted_at >= ^ts)

def info(query \\ __MODULE__) do
from(b in query,
select: %{
all: count(b.id),
failed: sum(fragment(@frag, b.status, type(^:failed, Status))),
running: sum(fragment(@frag, b.status, type(^:running, Status))),
successful: sum(fragment(@frag, b.status, type(^:successful, Status))),
queued: sum(fragment(@frag, b.status, type(^:queued, Status)))
}
)
end

def with_status(query \\ __MODULE__, status) do
from(b in query, where: b.status == ^status)
end
Expand Down
19 changes: 10 additions & 9 deletions lib/console/schema/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ defmodule Console.Schema.User do
@email_re ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-\.]+\.[a-zA-Z]{2,}$/

schema "watchman_users" do
field :name, :string
field :email, :string
field :bot_name, :string
field :password_hash, :string
field :profile, :string
field :password, :string, virtual: true
field :jwt, :string, virtual: true
field :deleted_at, :utc_datetime_usec
field :read_timestamp, :utc_datetime_usec
field :name, :string
field :email, :string
field :bot_name, :string
field :password_hash, :string
field :profile, :string
field :password, :string, virtual: true
field :jwt, :string, virtual: true
field :deleted_at, :utc_datetime_usec
field :read_timestamp, :utc_datetime_usec
field :build_timestamp, :utc_datetime_usec

embeds_one :roles, Roles, on_replace: :update do
field :admin, :boolean, default: false
Expand Down
10 changes: 7 additions & 3 deletions lib/console/services/users.ex
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,16 @@ defmodule Console.Services.Users do
|> validate_password(password)
end

@spec mark_read(User.t) :: user_resp
def mark_read(%User{} = user) do
Ecto.Changeset.change(user, %{read_timestamp: Timex.now()})
@spec mark_read(User.t, :read | :build) :: user_resp
def mark_read(%User{} = user, type \\ :read) do
key = read_timestamp(type)
Ecto.Changeset.change(user, %{key => Timex.now()})
|> Repo.update()
end

defp read_timestamp(:build), do: :build_timestamp
defp read_timestamp(_), do: :read_timestamp

defp validate_password(%User{deleted_at: nil} = user, pwd) do
case Argon2.check_pass(user, pwd) do
{:ok, user} -> {:ok, user}
Expand Down
9 changes: 9 additions & 0 deletions priv/repo/migrations/20230125163535_add_build_timestamp.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule Console.Repo.Migrations.AddBuildTimestamp do
use Ecto.Migration

def change do
alter table(:watchman_users) do
add :build_timestamp, :utc_datetime_usec
end
end
end
14 changes: 14 additions & 0 deletions test/console/graphql/mutations/user_mutations_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,18 @@ defmodule Console.GraphQl.UserMutationsTest do
assert read["readTimestamp"]
end
end

describe "markRead" do
test "it can mark the build timestamp for a user" do
user = insert(:user)

{:ok, %{data: %{"markRead" => read}}} = run_query("""
mutation Read($type: ReadType) {
markRead(type: $type) { buildTimestamp }
}
""", %{"type" => "BUILD"}, %{current_user: user})

assert read["buildTimestamp"]
end
end
end
24 changes: 24 additions & 0 deletions test/console/graphql/queries/build_queries_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,30 @@ defmodule Console.GraphQl.BuildQueriesTest do
end
end

describe "info" do
test "it can aggregate all builds after your read timestamp" do
old = Timex.now() |> Timex.shift(hours: -1)
user = insert(:user, build_timestamp: old)
insert_list(2, :build, status: :failed)
insert_list(3, :build, status: :successful)
insert(:build, status: :queued)
insert_list(5, :build, status: :running)
insert(:build, inserted_at: Timex.shift(old, hours: -1))

{:ok, %{data: %{"buildInfo" => info}}} = run_query("""
query {
buildInfo { all running queued successful failed }
}
""", %{}, %{current_user: user})

assert info["all"] == 11
assert info["running"] == 5
assert info["queued"] == 1
assert info["successful"] == 3
assert info["failed"] == 2
end
end

describe "build" do
test "It can sideload commands for a build" do
build = insert(:build)
Expand Down