mirror of
https://github.com/mirego/accent.git
synced 2024-10-26 18:39:53 +03:00
Add Azure Storage Container integration with new execution pattern (#402)
* Account Name working E2E. * Account key and container name now functional too * Form updated to only display bucket configuration * Functional UI * WIP * Adding and editing an integration fully works now! * Try to make build pass * Adjust test * Use mutation correctly in frontend * Refactor integration execution to be less tied to 'push strings to Azure' * Add execute azure storage container to upload files * Add tests --------- Co-authored-by: Guillaume Mercier <hello@guillaumemercier.com>
This commit is contained in:
parent
adccd44a66
commit
de6dd818ee
@ -71,6 +71,7 @@ defmodule Accent.RoleAbilities do
|
||||
revoke_project_api_token
|
||||
index_project_integrations
|
||||
create_project_integration
|
||||
execute_project_integration
|
||||
update_project_integration
|
||||
delete_project_integration
|
||||
create_version
|
||||
|
95
lib/accent/integrations/execute/azure_storage_container.ex
Normal file
95
lib/accent/integrations/execute/azure_storage_container.ex
Normal file
@ -0,0 +1,95 @@
|
||||
defmodule Accent.IntegrationManager.Execute.AzureStorageContainer do
|
||||
@moduledoc false
|
||||
|
||||
alias Accent.Document
|
||||
alias Accent.Repo
|
||||
alias Accent.Revision
|
||||
alias Accent.Scopes.Document, as: DocumentScope
|
||||
alias Accent.Scopes.Revision, as: RevisionScope
|
||||
alias Accent.Scopes.Translation, as: TranslationScope
|
||||
alias Accent.Scopes.Version, as: VersionScope
|
||||
alias Accent.Translation
|
||||
alias Accent.Version
|
||||
|
||||
def upload_translations(integration, params) do
|
||||
project = Repo.one!(Ecto.assoc(integration, :project))
|
||||
version = fetch_version(project, params)
|
||||
documents = fetch_documents(project)
|
||||
revisions = fetch_revisions(project)
|
||||
master_revision = Repo.preload(Repo.one!(RevisionScope.master(Ecto.assoc(project, :revisions))), :language)
|
||||
|
||||
uploads =
|
||||
Enum.flat_map(documents, fn document ->
|
||||
Enum.flat_map(revisions, fn revision ->
|
||||
translations = fetch_translations(document, revision, version)
|
||||
|
||||
if Enum.any?(translations) do
|
||||
render_options = %{
|
||||
translations: translations,
|
||||
master_language: Revision.language(master_revision),
|
||||
language: Revision.language(revision),
|
||||
document: document
|
||||
}
|
||||
|
||||
%{render: render} = Accent.TranslationsRenderer.render_translations(render_options)
|
||||
[%{document: %{document | render: render}, language: Accent.Revision.language(revision)}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
for upload <- uploads do
|
||||
file = Path.join([System.tmp_dir(), Accent.Utils.SecureRandom.urlsafe_base64(16)])
|
||||
:ok = File.write(file, upload.document.render)
|
||||
|
||||
uri = URI.parse(integration.data.azure_storage_container_sas)
|
||||
extension = Accent.DocumentFormat.extension_by_format(upload.document.format)
|
||||
|
||||
path =
|
||||
Path.join([
|
||||
uri.path,
|
||||
(version && version.tag) || "latest",
|
||||
upload.language.slug,
|
||||
upload.document.path <> "." <> extension
|
||||
])
|
||||
|
||||
HTTPoison.put(URI.to_string(%{uri | path: path}), {:file, file}, [{"x-ms-blob-type", "BlockBlob"}])
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp fetch_version(project, %{target_version: :specific, tag: tag}) do
|
||||
Version
|
||||
|> VersionScope.from_project(project.id)
|
||||
|> VersionScope.from_tag(tag)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
defp fetch_version(_, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp fetch_documents(project) do
|
||||
Document
|
||||
|> DocumentScope.from_project(project.id)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
defp fetch_revisions(project) do
|
||||
Revision
|
||||
|> RevisionScope.from_project(project.id)
|
||||
|> Repo.all()
|
||||
|> Repo.preload(:language)
|
||||
end
|
||||
|
||||
defp fetch_translations(document, revision, version) do
|
||||
Translation
|
||||
|> TranslationScope.active()
|
||||
|> TranslationScope.from_document(document.id)
|
||||
|> TranslationScope.from_revision(revision.id)
|
||||
|> TranslationScope.from_version(version && version.id)
|
||||
|> Repo.all()
|
||||
end
|
||||
end
|
@ -4,6 +4,7 @@ defmodule Accent.IntegrationManager do
|
||||
|
||||
alias Accent.Integration
|
||||
alias Accent.Repo
|
||||
alias Accent.User
|
||||
|
||||
@spec create(map()) :: {:ok, Integration.t()} | {:error, Ecto.Changeset.t()}
|
||||
def create(params) do
|
||||
@ -20,6 +21,22 @@ defmodule Accent.IntegrationManager do
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@spec execute(Integration.t(), User.t(), map()) :: {:ok, Integration.t()}
|
||||
def execute(integration, user, params) do
|
||||
case execute_integration(integration, params) do
|
||||
:ok ->
|
||||
integration
|
||||
|> change(%{last_executed_at: DateTime.utc_now(), last_executed_by_user_id: user.id})
|
||||
|> force_change(:updated_at, integration.updated_at)
|
||||
|> Repo.update!()
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
{:ok, integration}
|
||||
end
|
||||
|
||||
@spec delete(Integration.t()) :: {:ok, Integration.t()} | {:error, Ecto.Changeset.t()}
|
||||
def delete(integration) do
|
||||
Repo.delete(integration)
|
||||
@ -28,12 +45,25 @@ defmodule Accent.IntegrationManager do
|
||||
defp changeset(model, params) do
|
||||
model
|
||||
|> cast(params, [:project_id, :user_id, :service, :events])
|
||||
|> validate_inclusion(:service, ~w(slack github discord))
|
||||
|> validate_inclusion(:service, ~w(slack github discord azure_storage_container))
|
||||
|> cast_embed(:data, with: changeset_data(params[:service] || model.service))
|
||||
|> foreign_key_constraint(:project_id)
|
||||
|> validate_required([:service, :data])
|
||||
end
|
||||
|
||||
defp execute_integration(%{service: "azure_storage_container"} = integration, params) do
|
||||
Accent.IntegrationManager.Execute.AzureStorageContainer.upload_translations(
|
||||
integration,
|
||||
params[:azure_storage_container]
|
||||
)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp execute_integration(_integration, _params) do
|
||||
:noop
|
||||
end
|
||||
|
||||
defp changeset_data("slack") do
|
||||
fn model, params ->
|
||||
model
|
||||
@ -58,6 +88,14 @@ defmodule Accent.IntegrationManager do
|
||||
end
|
||||
end
|
||||
|
||||
defp changeset_data("azure_storage_container") do
|
||||
fn model, params ->
|
||||
model
|
||||
|> cast(params, [:azure_storage_container_sas])
|
||||
|> validate_required([:azure_storage_container_sas])
|
||||
end
|
||||
end
|
||||
|
||||
defp changeset_data(_) do
|
||||
fn model, params -> cast(model, params, []) end
|
||||
end
|
||||
|
@ -2,9 +2,15 @@ defmodule Accent.DocumentFormat do
|
||||
@moduledoc false
|
||||
defmacro ids, do: Enum.map(Langue.modules(), & &1.id)
|
||||
|
||||
def all,
|
||||
do:
|
||||
Enum.map(Langue.modules(), fn module ->
|
||||
%{name: module.display_name(), slug: module.id(), extension: module.extension()}
|
||||
end)
|
||||
def all do
|
||||
Enum.map(Langue.modules(), fn module ->
|
||||
%{name: module.display_name(), slug: module.id(), extension: module.extension()}
|
||||
end)
|
||||
end
|
||||
|
||||
def extension_by_format(slug) do
|
||||
Enum.find_value(Langue.modules(), fn module ->
|
||||
module.id() === slug && module.extension()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
@ -3,6 +3,7 @@ defmodule Accent.Integration do
|
||||
use Accent.Schema
|
||||
|
||||
schema "integrations" do
|
||||
field(:last_executed_at, :utc_datetime_usec)
|
||||
field(:service, :string)
|
||||
field(:events, {:array, :string})
|
||||
|
||||
@ -11,10 +12,12 @@ defmodule Accent.Integration do
|
||||
field(:repository)
|
||||
field(:token)
|
||||
field(:default_ref)
|
||||
field(:azure_storage_container_sas)
|
||||
end
|
||||
|
||||
belongs_to(:project, Accent.Project)
|
||||
belongs_to(:user, Accent.User)
|
||||
belongs_to(:last_executed_by_user, Accent.User)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -7,12 +7,23 @@ defmodule Accent.GraphQL.Mutations.Integration do
|
||||
|
||||
alias Accent.GraphQL.Resolvers.Integration, as: IntegrationResolver
|
||||
|
||||
enum :project_integration_execute_azure_storage_container_target_version do
|
||||
value(:specific)
|
||||
value(:latest)
|
||||
end
|
||||
|
||||
input_object :project_integration_execute_azure_storage_container_input do
|
||||
field(:target_version, :project_integration_execute_azure_storage_container_target_version)
|
||||
field(:tag, :string)
|
||||
end
|
||||
|
||||
input_object :project_integration_data_input do
|
||||
field(:id, :id)
|
||||
field(:url, :string)
|
||||
field(:repository, :string)
|
||||
field(:default_ref, :string)
|
||||
field(:token, :string)
|
||||
field(:azure_storage_container_sas, :string)
|
||||
end
|
||||
|
||||
payload_object(:project_integration_payload, :project_integration)
|
||||
@ -28,6 +39,14 @@ defmodule Accent.GraphQL.Mutations.Integration do
|
||||
middleware(&build_payload/2)
|
||||
end
|
||||
|
||||
field :execute_project_integration, :project_integration_payload do
|
||||
arg(:id, non_null(:id))
|
||||
arg(:azure_storage_container, :project_integration_execute_azure_storage_container_input)
|
||||
|
||||
resolve(integration_authorize(:execute_project_integration, &IntegrationResolver.execute/3))
|
||||
middleware(&build_payload/2)
|
||||
end
|
||||
|
||||
field :update_project_integration, :project_integration_payload do
|
||||
arg(:id, non_null(:id))
|
||||
arg(:service, non_null(:project_integration_service))
|
||||
|
@ -25,6 +25,13 @@ defmodule Accent.GraphQL.Resolvers.Integration do
|
||||
|> build()
|
||||
end
|
||||
|
||||
@spec execute(Integration.t(), map(), GraphQLContext.t()) :: integration_operation
|
||||
def execute(integration, args, info) do
|
||||
integration
|
||||
|> IntegrationManager.execute(info.context[:conn].assigns[:current_user], args)
|
||||
|> build()
|
||||
end
|
||||
|
||||
@spec delete(Integration.t(), map(), GraphQLContext.t()) :: integration_operation
|
||||
def delete(integration, _args, _info) do
|
||||
integration
|
||||
|
@ -6,6 +6,7 @@ defmodule Accent.GraphQL.Types.Integration do
|
||||
value(:slack, as: "slack")
|
||||
value(:discord, as: "discord")
|
||||
value(:github, as: "github")
|
||||
value(:azure_storage_container, as: "azure_storage_container")
|
||||
end
|
||||
|
||||
enum :project_integration_event do
|
||||
@ -23,7 +24,8 @@ defmodule Accent.GraphQL.Types.Integration do
|
||||
resolve_type(fn
|
||||
%{service: "discord"}, _ -> :project_integration_discord
|
||||
%{service: "slack"}, _ -> :project_integration_slack
|
||||
%{service: "github"}, _ -> :project_integration_git_hub
|
||||
%{service: "github"}, _ -> :project_integration_github
|
||||
%{service: "azure_storage_container"}, _ -> :project_integration_azure_storage_container
|
||||
end)
|
||||
end
|
||||
|
||||
@ -45,7 +47,7 @@ defmodule Accent.GraphQL.Types.Integration do
|
||||
interfaces([:project_integration])
|
||||
end
|
||||
|
||||
object :project_integration_git_hub do
|
||||
object :project_integration_github do
|
||||
field(:id, non_null(:id))
|
||||
field(:service, non_null(:project_integration_service))
|
||||
field(:events, non_null(list_of(non_null(:project_integration_event))))
|
||||
@ -54,6 +56,15 @@ defmodule Accent.GraphQL.Types.Integration do
|
||||
interfaces([:project_integration])
|
||||
end
|
||||
|
||||
object :project_integration_azure_storage_container do
|
||||
field(:id, non_null(:id))
|
||||
field(:service, non_null(:project_integration_service))
|
||||
field(:last_executed_at, :datetime)
|
||||
field(:data, non_null(:project_integration_azure_data))
|
||||
|
||||
interfaces([:project_integration])
|
||||
end
|
||||
|
||||
object :project_integration_slack_data do
|
||||
field(:id, non_null(:id))
|
||||
field(:url, non_null(:string))
|
||||
@ -62,7 +73,18 @@ defmodule Accent.GraphQL.Types.Integration do
|
||||
object :project_integration_github_data do
|
||||
field(:id, non_null(:id))
|
||||
field(:repository, non_null(:string))
|
||||
field(:token, non_null(:string))
|
||||
field(:default_ref, non_null(:string))
|
||||
end
|
||||
|
||||
object :project_integration_azure_data do
|
||||
field(:id, non_null(:id))
|
||||
|
||||
field(:sas_base_url, non_null(:string),
|
||||
resolve: fn data, _, _ ->
|
||||
uri = URI.parse(data.azure_storage_container_sas)
|
||||
uri = URI.to_string(%{uri | query: nil})
|
||||
{:ok, uri}
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
2
mix.exs
2
mix.exs
@ -43,7 +43,7 @@ defmodule Accent.Mixfile do
|
||||
# Plugs
|
||||
{:plug_assign, "~> 1.0.0"},
|
||||
{:canada, "~> 2.0.0", override: true},
|
||||
{:canary, "~> 1.1.0"},
|
||||
{:canary, github: "runhyve/canary"},
|
||||
{:corsica, "~> 2.0"},
|
||||
{:bandit, "~> 1.0"},
|
||||
{:plug, "1.14.2"},
|
||||
|
2
mix.lock
2
mix.lock
@ -9,7 +9,7 @@
|
||||
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
|
||||
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
|
||||
"canada": {:hex, :canada, "2.0.0", "ce5e058f576a0625959fc5427fcde15311fb28a5ebc13775eafd13468ad16553", [:mix], [], "hexpm", "49a648c48d8b0864380f38f02a7f316bd30fd45602205c48197432b5225d8596"},
|
||||
"canary": {:hex, :canary, "1.1.1", "4138d5e05db8497c477e4af73902eb9ae06e49dceaa13c2dd9f0b55525ded48b", [:mix], [{:canada, "~> 1.0.1", [hex: :canada, repo: "hexpm", optional: false]}, {:ecto, ">= 1.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f348d9848693c830a65b707bba9e4dfdd6434e8c356a8d4477e4535afb0d653b"},
|
||||
"canary": {:git, "https://github.com/runhyve/canary.git", "b81d780e1cb7a1c276599f980ab9c9a7c9cd8c12", []},
|
||||
"castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"},
|
||||
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
||||
"cloak": {:hex, :cloak, "1.1.2", "7e0006c2b0b98d976d4f559080fabefd81f0e0a50a3c4b621f85ceeb563e80bb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "940d5ac4fcd51b252930fd112e319ea5ae6ab540b722f3ca60a85666759b9585"},
|
||||
|
@ -0,0 +1,11 @@
|
||||
defmodule Accent.Repo.Migrations.AddLastExecutedForIntegrations do
|
||||
@moduledoc false
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:integrations) do
|
||||
add(:last_executed_at, :utc_datetime_usec)
|
||||
add(:last_executed_by_user_id, references(:users, type: :uuid))
|
||||
end
|
||||
end
|
||||
end
|
@ -109,7 +109,8 @@ defmodule AccentTest.GraphQL.Resolvers.Integration do
|
||||
{:ok, integration} = Resolver.create(project, %{service: "foo", data: %{url: ""}}, context)
|
||||
|
||||
assert integration.errors == [
|
||||
service: {"is invalid", [validation: :inclusion, enum: ["slack", "github", "discord"]]}
|
||||
service:
|
||||
{"is invalid", [validation: :inclusion, enum: ["slack", "github", "discord", "azure_storage_container"]]}
|
||||
]
|
||||
|
||||
assert Repo.all(Integration) == []
|
||||
|
116
test/services/integration_manager_test.exs
Normal file
116
test/services/integration_manager_test.exs
Normal file
@ -0,0 +1,116 @@
|
||||
defmodule AccentTest.IntegrationManager do
|
||||
@moduledoc false
|
||||
use Accent.RepoCase, async: true
|
||||
|
||||
import Mock
|
||||
|
||||
alias Accent.Document
|
||||
alias Accent.Integration
|
||||
alias Accent.IntegrationManager
|
||||
alias Accent.Language
|
||||
alias Accent.Project
|
||||
alias Accent.Repo
|
||||
alias Accent.Revision
|
||||
alias Accent.Translation
|
||||
alias Accent.User
|
||||
alias Accent.Version
|
||||
|
||||
describe "execute" do
|
||||
setup do
|
||||
project = Repo.insert!(%Project{main_color: "red", name: "com"})
|
||||
user = Repo.insert!(%User{email: "test@test.com"})
|
||||
language = Repo.insert!(%Language{slug: "fr-custom", name: "Fr"})
|
||||
revision = Repo.insert!(%Revision{project: project, language: language})
|
||||
document = Repo.insert!(%Document{project: project, path: "foo", format: "gettext"})
|
||||
|
||||
{:ok, [project: project, user: user, language: language, revision: revision, document: document]}
|
||||
end
|
||||
|
||||
test "azure storage container with version", %{
|
||||
user: user,
|
||||
revision: revision,
|
||||
document: document,
|
||||
project: project
|
||||
} do
|
||||
version = Repo.insert!(%Version{project: project, tag: "1.2.45", name: "vNext", user: user})
|
||||
|
||||
Repo.insert!(%Translation{
|
||||
revision: revision,
|
||||
document: document,
|
||||
key: "key",
|
||||
corrected_text: "value latest"
|
||||
})
|
||||
|
||||
Repo.insert!(%Translation{
|
||||
revision: revision,
|
||||
version: version,
|
||||
document: document,
|
||||
key: "key",
|
||||
corrected_text: "value v1.2.45"
|
||||
})
|
||||
|
||||
integration =
|
||||
Repo.insert!(%Integration{
|
||||
project: project,
|
||||
user: user,
|
||||
service: "azure_storage_container",
|
||||
data: %{azure_storage_container_sas: "http://azure.blob.test/container?sas=1234"}
|
||||
})
|
||||
|
||||
with_mock HTTPoison,
|
||||
put: fn url, {:file, file}, headers ->
|
||||
content = File.read!(file)
|
||||
|
||||
assert content === """
|
||||
msgid "key"
|
||||
msgstr "value v1.2.45"
|
||||
"""
|
||||
|
||||
assert String.ends_with?(url, "1.2.45/fr-custom/foo.po?sas=1234")
|
||||
assert headers === [{"x-ms-blob-type", "BlockBlob"}]
|
||||
{:ok, nil}
|
||||
end do
|
||||
IntegrationManager.execute(integration, user, %{
|
||||
azure_storage_container: %{target_version: :specific, tag: "1.2.45"}
|
||||
})
|
||||
end
|
||||
|
||||
updated_integration = Repo.reload!(integration)
|
||||
|
||||
assert updated_integration.last_executed_at
|
||||
assert updated_integration.last_executed_by_user_id === user.id
|
||||
end
|
||||
|
||||
test "azure storage container latest version", %{
|
||||
user: user,
|
||||
revision: revision,
|
||||
document: document,
|
||||
project: project
|
||||
} do
|
||||
Repo.insert!(%Translation{revision: revision, document: document, key: "key", corrected_text: "value"})
|
||||
|
||||
integration =
|
||||
Repo.insert!(%Integration{
|
||||
project: project,
|
||||
user: user,
|
||||
service: "azure_storage_container",
|
||||
data: %{azure_storage_container_sas: "http://azure.blob.test/container?sas=1234"}
|
||||
})
|
||||
|
||||
with_mock HTTPoison,
|
||||
put: fn url, body, headers ->
|
||||
assert match?({:file, _}, body)
|
||||
assert String.ends_with?(url, "latest/fr-custom/foo.po?sas=1234")
|
||||
assert headers === [{"x-ms-blob-type", "BlockBlob"}]
|
||||
{:ok, nil}
|
||||
end do
|
||||
IntegrationManager.execute(integration, user, %{azure_storage_container: %{target_version: :latest}})
|
||||
end
|
||||
|
||||
updated_integration = Repo.reload!(integration)
|
||||
|
||||
assert updated_integration.last_executed_at
|
||||
assert updated_integration.last_executed_by_user_id === user.id
|
||||
end
|
||||
end
|
||||
end
|
@ -491,7 +491,9 @@
|
||||
"cancel": "Cancel",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"last_executed_at": "Last executed at:",
|
||||
"data": {
|
||||
"azure_storage_container_sas": "SAS URL",
|
||||
"default_ref": "Default ref",
|
||||
"repository": "Repository",
|
||||
"token": "Token",
|
||||
@ -510,6 +512,23 @@
|
||||
"new_conflicts": "New strings to review",
|
||||
"complete_review": "Project is 100% reviewed"
|
||||
}
|
||||
},
|
||||
"execute": {
|
||||
"azure_storage_container": {
|
||||
"title": "Upload files to Azure Storage Container",
|
||||
"cancel_button": "Cancel",
|
||||
"error": "Error while pushing to Azure. Check your credentials and try again.",
|
||||
"push_button": "Upload",
|
||||
"sas_base_url": "Base upload URL",
|
||||
"submit_confirm": "Uploading files to Azure Storage Container can override existing files if you upload for the latest or an already uploaded version tag.",
|
||||
"target_version": {
|
||||
"label": "Target Version",
|
||||
"options": {
|
||||
"latest": "Latest Version",
|
||||
"specific": "Specific Version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -959,7 +978,7 @@
|
||||
"DISCORD": "Discord",
|
||||
"GITHUB": "GitHub",
|
||||
"SLACK": "Slack",
|
||||
"MICROSOFT": "Microsoft"
|
||||
"AZURE_STORAGE_CONTAINER": "Azure Storage Container"
|
||||
},
|
||||
"search_input_placeholder_text": "Search for a string…"
|
||||
},
|
||||
|
@ -506,8 +506,10 @@
|
||||
"save": "Sauver",
|
||||
"cancel": "Annuler",
|
||||
"edit": "Éditer",
|
||||
"last_executed_at": "Dernière exécution:",
|
||||
"delete": "Supprimer",
|
||||
"data": {
|
||||
"azure_storage_container_sas": "SAS URL",
|
||||
"default_ref": "Réf par défaut",
|
||||
"repository": "Dépôt",
|
||||
"token": "Jeton",
|
||||
@ -526,6 +528,23 @@
|
||||
"new_conflicts": "Nouvelles chaîne à réviser",
|
||||
"complete_review": "Le projet est revu à 100%"
|
||||
}
|
||||
},
|
||||
"execute": {
|
||||
"azure_storage_container": {
|
||||
"title": "Publier sur Azure Storage Container",
|
||||
"cancel_button": "Annuler",
|
||||
"error": "Une erreur s’est produite lors de la publication sur Azure. Veuillez vérifier vos informations d’identification et réessayer.",
|
||||
"push_button": "Publier",
|
||||
"sas_base_url": "Base de l’URL de publication",
|
||||
"submit_confirm": "La publication de fichiers vers Azure Storage Container peut remplacer les fichiers existants si vous publiez la dernière version ou une version déjà publiée.",
|
||||
"target_version": {
|
||||
"label": "Version Cible",
|
||||
"options": {
|
||||
"latest": "Dernière version",
|
||||
"specific": "Version spécifique"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -959,7 +978,7 @@
|
||||
"DISCORD": "Discord",
|
||||
"GITHUB": "GitHub",
|
||||
"SLACK": "Slack",
|
||||
"MICROSOFT": "Microsoft"
|
||||
"AZURE_STORAGE_CONTAINER": "Azure Storage Container"
|
||||
},
|
||||
"search_input_placeholder_text": "Rechercher une chaîne…"
|
||||
},
|
||||
|
67
webapp/app/pods/components/azure-push-form/component.ts
Normal file
67
webapp/app/pods/components/azure-push-form/component.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
import {action} from '@ember/object';
|
||||
|
||||
interface Args {
|
||||
error: boolean;
|
||||
project: any;
|
||||
onPush: ({
|
||||
targetVersion,
|
||||
specificVersion,
|
||||
}: {
|
||||
targetVersion: string;
|
||||
specificVersion: string | null;
|
||||
}) => Promise<void>;
|
||||
}
|
||||
|
||||
export default class AzurePushForm extends Component<Args> {
|
||||
allTargetVersions = [
|
||||
{
|
||||
value: 'LATEST',
|
||||
label:
|
||||
'components.project_settings.integrations.target_version.options.latest',
|
||||
},
|
||||
{
|
||||
value: 'SPECIFIC',
|
||||
label:
|
||||
'components.project_settings.integrations.target_version.options.specific',
|
||||
},
|
||||
{
|
||||
value: 'ALL',
|
||||
label:
|
||||
'components.project_settings.integrations.target_version.options.all',
|
||||
},
|
||||
];
|
||||
|
||||
@tracked
|
||||
targetVersion = this.allTargetVersions[0].value;
|
||||
|
||||
@tracked
|
||||
specificVersion: string | null;
|
||||
|
||||
@tracked
|
||||
isSubmitting = false;
|
||||
|
||||
@action
|
||||
async submit() {
|
||||
this.isSubmitting = true;
|
||||
|
||||
await this.args.onPush({
|
||||
targetVersion: this.targetVersion,
|
||||
specificVersion: this.specificVersion,
|
||||
});
|
||||
|
||||
this.isSubmitting = false;
|
||||
}
|
||||
|
||||
@action
|
||||
setTargetVersion(targetVersion: string) {
|
||||
this.targetVersion = targetVersion;
|
||||
}
|
||||
@action
|
||||
setSpecificVersion(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
||||
this.specificVersion = target.value;
|
||||
}
|
||||
}
|
50
webapp/app/pods/components/azure-push-form/styles.scss
Normal file
50
webapp/app/pods/components/azure-push-form/styles.scss
Normal file
@ -0,0 +1,50 @@
|
||||
.azure-push-form {
|
||||
padding: 20px;
|
||||
background: var(--content-background);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
font-size: 27px;
|
||||
font-weight: 300;
|
||||
color: var(--color-primary);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.textInput {
|
||||
@extend %textInput;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
padding: 10px;
|
||||
min-width: 250px;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
font-family: var(--font-primary);
|
||||
}
|
||||
|
||||
.errors {
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin-bottom: 5px;
|
||||
color: var(--color-error);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.formItem {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.formActions {
|
||||
padding-top: 10px;
|
||||
}
|
33
webapp/app/pods/components/azure-push-form/template.hbs
Normal file
33
webapp/app/pods/components/azure-push-form/template.hbs
Normal file
@ -0,0 +1,33 @@
|
||||
<div {{did-insert this.focusTextarea}} local-class='azure-push-form'>
|
||||
<h1 local-class='title'>
|
||||
{{t 'components.project_settings.integrations.execute.azure_storage_container.title'}}
|
||||
</h1>
|
||||
|
||||
{{#if @error}}
|
||||
<div local-class='errors'>
|
||||
<div local-class='error'>
|
||||
{{t 'components.project_settings.integrations.execute.azure_storage_container.error'}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div local-class='formItem'>
|
||||
<ProjectSettings::Integrations::Form::DataControlRadio
|
||||
@allTargetVersions={{this.allTargetVersions}}
|
||||
@targetVersion={{this.targetVersion}}
|
||||
@specificVersion={{this.specificVersion}}
|
||||
@onChangeTargetVersion={{this.setTargetVersion}}
|
||||
@onChangeSpecificVersion={{this.setSpecificVersion}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div local-class='formActions'>
|
||||
<LinkTo @route='logged-in.project.edit.service-integrations' @model={{@project.id}} class='button button--filled button--white'>
|
||||
{{t 'components.project_settings.integrations.execute.azure_storage_container.cancel_button'}}
|
||||
</LinkTo>
|
||||
|
||||
<AsyncButton class='button button--filled' @loading={{this.isSubmitting}} @onClick={{this.submit}}>
|
||||
{{t 'components.project_settings.integrations.execute.azure_storage_container.push_button'}}
|
||||
</AsyncButton>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,17 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
|
||||
interface Args {
|
||||
errors: any;
|
||||
project: any;
|
||||
onChangeSas: (url: string) => void;
|
||||
}
|
||||
|
||||
export default class AzureStorageContainer extends Component<Args> {
|
||||
@action
|
||||
changeSas(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
||||
this.args.onChangeSas(target.value);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
.instructions {
|
||||
border-top: 1px solid var(--background-light-highlight);
|
||||
padding-top: 10px;
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
.instructions-text {
|
||||
margin-top: 7px;
|
||||
font-style: italic;
|
||||
color: #555;
|
||||
|
||||
a {
|
||||
font-family: var(--font-monospace);
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data-control {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.data-title {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.data-title-help {
|
||||
margin-left: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
text-decoration: none;
|
||||
color: var(--color-primary);
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.textInput {
|
||||
@extend %textInput;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
padding: 8px 10px;
|
||||
margin-right: 10px;
|
||||
background: #fafafa;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 11px;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<ProjectSettings::Integrations::Form::DataControlText
|
||||
@error={{field-error @errors 'data.azureStorageContainerSas'}}
|
||||
@label={{t 'components.project_settings.integrations.data.azure_storage_container_sas'}}
|
||||
@helpLinkTitle='How to create a SAS URL?'
|
||||
@helpLinkHref='https://learn.microsoft.com/en-us/rest/api/storageservices/delegate-access-with-shared-access-signature'
|
||||
@placeholder='https://<account-name>.blob.core.windows.net/<container-name>/?<SAS-token>'
|
||||
@onChange={{this.changeSas}}
|
||||
/>
|
@ -6,6 +6,7 @@ import IntlService from 'ember-intl/services/intl';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const LOGOS = {
|
||||
AZURE_STORAGE_CONTAINER: 'assets/services/azure.svg',
|
||||
DISCORD: 'assets/services/discord.svg',
|
||||
GITHUB: 'assets/services/github.svg',
|
||||
SLACK: 'assets/services/slack.svg',
|
||||
@ -17,7 +18,7 @@ interface Args {
|
||||
service,
|
||||
events,
|
||||
integration,
|
||||
data: {url, repository, token, defaultRef},
|
||||
data: {url, repository, token, defaultRef, azureStorageContainerSas},
|
||||
}: {
|
||||
service: any;
|
||||
events: any;
|
||||
@ -27,6 +28,7 @@ interface Args {
|
||||
repository: string;
|
||||
token: string;
|
||||
defaultRef: string;
|
||||
azureStorageContainerSas: string;
|
||||
};
|
||||
}) => Promise<{errors: any}>;
|
||||
onCancel: () => void;
|
||||
@ -62,10 +64,16 @@ export default class IntegrationsForm extends Component<Args> {
|
||||
@tracked
|
||||
token: string;
|
||||
|
||||
@tracked
|
||||
azureStorageContainerSas: string;
|
||||
|
||||
@tracked
|
||||
azureStorageContainerSasBaseUrl: string;
|
||||
|
||||
@tracked
|
||||
defaultRef = 'main';
|
||||
|
||||
services = ['SLACK', 'GITHUB', 'DISCORD'];
|
||||
services = ['AZURE_STORAGE_CONTAINER', 'SLACK', 'GITHUB', 'DISCORD'];
|
||||
|
||||
@not('url')
|
||||
emptyUrl: boolean;
|
||||
@ -105,7 +113,6 @@ export default class IntegrationsForm extends Component<Args> {
|
||||
data: {
|
||||
url: this.url,
|
||||
repository: this.repository,
|
||||
token: this.token,
|
||||
defaultRef: this.defaultRef,
|
||||
},
|
||||
};
|
||||
@ -115,8 +122,8 @@ export default class IntegrationsForm extends Component<Args> {
|
||||
this.url = this.integration.data.url;
|
||||
this.events = this.integration.events;
|
||||
this.repository = this.integration.data.repository;
|
||||
this.token = this.integration.data.token;
|
||||
this.defaultRef = this.integration.data.defaultRef;
|
||||
this.azureStorageContainerSasBaseUrl = this.integration.data.sasBaseUrl;
|
||||
}
|
||||
|
||||
@action
|
||||
@ -154,6 +161,11 @@ export default class IntegrationsForm extends Component<Args> {
|
||||
this.defaultRef = defaultRef;
|
||||
}
|
||||
|
||||
@action
|
||||
setAzureStorageContainerSas(sas: string) {
|
||||
this.azureStorageContainerSas = sas;
|
||||
}
|
||||
|
||||
@action
|
||||
async submit() {
|
||||
this.isSubmiting = true;
|
||||
@ -167,6 +179,7 @@ export default class IntegrationsForm extends Component<Args> {
|
||||
repository: this.repository,
|
||||
token: this.token,
|
||||
defaultRef: this.defaultRef,
|
||||
azureStorageContainerSas: this.azureStorageContainerSas,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -25,10 +25,6 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.readonly-service {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -34,6 +34,7 @@
|
||||
onChangeRepository=(fn this.setRepository)
|
||||
onChangeToken=(fn this.setToken)
|
||||
onChangeDefaultRef=(fn this.setDefaultRef)
|
||||
onChangeSas=(fn this.setAzureStorageContainerSas)
|
||||
}}
|
||||
</div>
|
||||
|
||||
|
@ -3,11 +3,14 @@ import Component from '@glimmer/component';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const LOGOS = {
|
||||
AZURE_STORAGE_CONTAINER: 'assets/services/azure.svg',
|
||||
DISCORD: 'assets/services/discord.svg',
|
||||
GITHUB: 'assets/services/github.svg',
|
||||
SLACK: 'assets/services/slack.svg',
|
||||
};
|
||||
|
||||
const EXECUTABLE_SERVICES = ['AZURE_STORAGE_CONTAINER'];
|
||||
|
||||
interface Args {
|
||||
project: any;
|
||||
permissions: Record<string, true>;
|
||||
@ -26,6 +29,13 @@ export default class IntegrationsListItem extends Component<Args> {
|
||||
@tracked
|
||||
isDeleting = false;
|
||||
|
||||
@tracked
|
||||
isExecuting = false;
|
||||
|
||||
get serviceIsExecutable() {
|
||||
return EXECUTABLE_SERVICES.includes(this.args.integration.service);
|
||||
}
|
||||
|
||||
get logoService() {
|
||||
const service: keyof typeof LOGOS = this.args.integration.service;
|
||||
|
||||
@ -36,6 +46,16 @@ export default class IntegrationsListItem extends Component<Args> {
|
||||
return `general.integration_services.${this.args.integration.service}`;
|
||||
}
|
||||
|
||||
get dataExecuteComponent() {
|
||||
return `project-settings/integrations/list/item/execute/${this.args.integration.service.toLowerCase()}`;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleExecuting() {
|
||||
this.errors = [];
|
||||
this.isExecuting = !this.isExecuting;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleEdit() {
|
||||
this.errors = [];
|
||||
|
@ -0,0 +1,105 @@
|
||||
import {inject as service} from '@ember/service';
|
||||
import {action} from '@ember/object';
|
||||
import {readOnly} from '@ember/object/computed';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
import Component from '@glimmer/component';
|
||||
import IntlService from 'ember-intl/services/intl';
|
||||
import FlashMessages from 'ember-cli-flash/services/flash-messages';
|
||||
import ApolloMutate from 'accent-webapp/services/apollo-mutate';
|
||||
import executeIntegration from 'accent-webapp/queries/execute-integration';
|
||||
|
||||
const FLASH_MESSAGE_CREATE_SUCCESS =
|
||||
'pods.versions.new.flash_messages.create_success';
|
||||
const FLASH_MESSAGE_CREATE_ERROR =
|
||||
'pods.versions.new.flash_messages.create_error';
|
||||
|
||||
interface Args {
|
||||
close: () => void;
|
||||
integration: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default class IntegrationExecuteAzureStorageContainer extends Component<Args> {
|
||||
@service('intl')
|
||||
intl: IntlService;
|
||||
|
||||
@service('apollo-mutate')
|
||||
apolloMutate: ApolloMutate;
|
||||
|
||||
@service('flash-messages')
|
||||
flashMessages: FlashMessages;
|
||||
|
||||
@readOnly('model.projectModel.project')
|
||||
project: any;
|
||||
|
||||
@tracked
|
||||
error = false;
|
||||
|
||||
allTargetVersions = [
|
||||
{
|
||||
value: 'LATEST',
|
||||
label:
|
||||
'components.project_settings.integrations.execute.azure_storage_container.target_version.options.latest',
|
||||
},
|
||||
{
|
||||
value: 'SPECIFIC',
|
||||
label:
|
||||
'components.project_settings.integrations.execute.azure_storage_container.target_version.options.specific',
|
||||
},
|
||||
];
|
||||
|
||||
@tracked
|
||||
targetVersion = this.allTargetVersions[0].value;
|
||||
|
||||
@tracked
|
||||
tag: string | null = null;
|
||||
|
||||
@tracked
|
||||
isSubmitting = false;
|
||||
|
||||
@action
|
||||
setTargetVersion(targetVersion: string) {
|
||||
this.tag = null;
|
||||
this.targetVersion = targetVersion;
|
||||
}
|
||||
|
||||
@action
|
||||
setTag(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
|
||||
this.tag = target.value;
|
||||
}
|
||||
|
||||
@action
|
||||
async submit() {
|
||||
const confirmMessage = this.intl.t(
|
||||
'components.project_settings.integrations.execute.azure_storage_container.submit_confirm'
|
||||
);
|
||||
/* eslint-disable-next-line no-alert */
|
||||
if (!window.confirm(confirmMessage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.apolloMutate.mutate({
|
||||
mutation: executeIntegration,
|
||||
refetchQueries: ['ProjectServiceIntegrations'],
|
||||
variables: {
|
||||
integrationId: this.args.integration.id,
|
||||
azureStorageContainer: {
|
||||
tag: this.tag,
|
||||
targetVersion: this.targetVersion,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (response.errors) {
|
||||
this.flashMessages.error(this.intl.t(FLASH_MESSAGE_CREATE_ERROR));
|
||||
} else {
|
||||
this.args.close();
|
||||
this.flashMessages.success(this.intl.t(FLASH_MESSAGE_CREATE_SUCCESS));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
.azure-push-form {
|
||||
padding: 20px;
|
||||
background: var(--content-background);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
font-size: 27px;
|
||||
font-weight: 300;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--background-light-highlight);
|
||||
font-size: 13px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
font-family: var(--font-monospace);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 13px;
|
||||
margin-bottom: 20px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.textInput {
|
||||
@extend %textInput;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
padding: 10px;
|
||||
min-width: 250px;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
font-family: var(--font-primary);
|
||||
}
|
||||
|
||||
.errors {
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin-bottom: 5px;
|
||||
color: var(--color-error);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.formActions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.data-control {
|
||||
.radio {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
border: 1px solid var(--background-light-highlight);
|
||||
padding: 4px 6px;
|
||||
border-radius: var(--border-radius);
|
||||
background: var(--input-background);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: 0.2s ease-in-out;
|
||||
transition-property: background;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: var(--background-light);
|
||||
}
|
||||
|
||||
input {
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.data-title {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
<div local-class='azure-push-form'>
|
||||
<h1 local-class='title'>
|
||||
{{t 'components.project_settings.integrations.execute.azure_storage_container.title'}}
|
||||
</h1>
|
||||
|
||||
<div local-class='info'>
|
||||
<div>
|
||||
<strong>{{t 'components.project_settings.integrations.execute.azure_storage_container.sas_base_url'}}</strong>
|
||||
<span>{{@integration.data.sasBaseUrl}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if this.error}}
|
||||
<div local-class='errors'>
|
||||
<div local-class='error'>
|
||||
{{t 'components.project_settings.integrations.execute.azure_storage_container.error'}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div local-class='formItem'>
|
||||
<div local-class='data-control'>
|
||||
<h3 local-class='data-title'>
|
||||
{{t 'components.project_settings.integrations.execute.azure_storage_container.target_version.label'}}
|
||||
</h3>
|
||||
|
||||
{{#each this.allTargetVersions as |target|}}
|
||||
<label local-class='radio'>
|
||||
<input type='radio' checked={{eq this.targetVersion target.value}} name='target_version' {{on 'change' (fn this.setTargetVersion target.value)}} required />
|
||||
{{t target.label}}
|
||||
</label>
|
||||
{{/each}}
|
||||
|
||||
{{#if (eq this.targetVersion 'SPECIFIC')}}
|
||||
<ProjectSettings::Integrations::Form::DataControlText @placeholder='1.0.0' @value={{this.tag}} @onChange={{this.setTag}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div local-class='formActions'>
|
||||
<AsyncButton class='button button--filled' @loading={{this.isSubmitting}} @onClick={{this.submit}}>
|
||||
{{inline-svg '/assets/arrow-up-right.svg' class='button-icon'}}
|
||||
{{t 'components.project_settings.integrations.execute.azure_storage_container.push_button'}}
|
||||
</AsyncButton>
|
||||
</div>
|
||||
</div>
|
@ -33,6 +33,12 @@
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
.details-last-executed-at {
|
||||
margin-left: 5px;
|
||||
font-size: 11px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.details-logo {
|
||||
flex: 0 0 20px;
|
||||
margin-right: 8px;
|
||||
@ -40,5 +46,7 @@
|
||||
}
|
||||
|
||||
.details-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
@ -18,10 +18,21 @@
|
||||
<span local-class='details-preview'>
|
||||
{{@integration.data.url}}
|
||||
{{@integration.data.repository}}
|
||||
{{@integration.data.sasBaseUrl}}
|
||||
|
||||
{{#if @integration.lastExecutedAt}}
|
||||
<span local-class='details-last-executed-at'>{{t 'components.project_settings.integrations.last_executed_at'}} {{time-ago-in-words @integration.lastExecutedAt}}</span>
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div local-class='details-actions'>
|
||||
{{#if this.serviceIsExecutable}}
|
||||
<button class='button button--filled button--iconOnly' {{on 'click' (fn this.toggleExecuting)}}>
|
||||
{{inline-svg '/assets/arrow-up-right.svg' class='button-icon'}}
|
||||
</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if (get @permissions 'update_project_integration')}}
|
||||
<button class='button button--filled button--white' {{on 'click' (fn this.toggleEdit)}}>
|
||||
{{t 'components.project_settings.integrations.edit'}}
|
||||
@ -36,4 +47,10 @@
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</li>
|
||||
</li>
|
||||
|
||||
{{#if this.isExecuting}}
|
||||
<AccModal @small={{true}} @onClose={{fn this.toggleExecuting}}>
|
||||
{{component this.dataExecuteComponent integration=@integration close=this.toggleExecuting}}
|
||||
</AccModal>
|
||||
{{/if}}
|
23
webapp/app/queries/execute-integration.ts
Normal file
23
webapp/app/queries/execute-integration.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export default gql`
|
||||
mutation IntegrationExecute(
|
||||
$integrationId: ID!
|
||||
$azureStorageContainer: ProjectIntegrationExecuteAzureStorageContainerInput
|
||||
) {
|
||||
executeProjectIntegration(
|
||||
id: $integrationId
|
||||
azureStorageContainer: $azureStorageContainer
|
||||
) {
|
||||
projectIntegration: result {
|
||||
id
|
||||
}
|
||||
|
||||
successful
|
||||
errors: messages {
|
||||
code
|
||||
field
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
@ -27,14 +27,21 @@ export default gql`
|
||||
}
|
||||
}
|
||||
|
||||
... on ProjectIntegrationGitHub {
|
||||
... on ProjectIntegrationGithub {
|
||||
data {
|
||||
id
|
||||
repository
|
||||
token
|
||||
defaultRef
|
||||
}
|
||||
}
|
||||
|
||||
... on ProjectIntegrationAzureStorageContainer {
|
||||
lastExecutedAt
|
||||
data {
|
||||
id
|
||||
sasBaseUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,10 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({
|
||||
kind: 'INTERFACE',
|
||||
name: 'ProjectIntegration',
|
||||
possibleTypes: [
|
||||
{name: 'ProjectIntegrationAzureStorageContainer'},
|
||||
{name: 'ProjectIntegrationDiscord'},
|
||||
{name: 'ProjectIntegrationSlack'},
|
||||
{name: 'ProjectIntegrationGitHub'},
|
||||
{name: 'ProjectIntegrationGithub'},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
12
webapp/public/assets/arrow-up-right.svg
Normal file
12
webapp/public/assets/arrow-up-right.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="7" y1="17" x2="17" y2="7"></line>
|
||||
<polyline points="7 7 17 7 17 17"></polyline>
|
||||
</svg>
|
After Width: | Height: | Size: 275 B |
64
webapp/public/assets/services/azure.svg
Normal file
64
webapp/public/assets/services/azure.svg
Normal file
@ -0,0 +1,64 @@
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 96 96"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="e399c19f-b68f-429d-b176-18c2117ff73c"
|
||||
x1="-1032.172"
|
||||
x2="-1059.213"
|
||||
y1="145.312"
|
||||
y2="65.426"
|
||||
gradientTransform="matrix(1 0 0 -1 1075 158)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stop-color="#114a8b" />
|
||||
<stop offset="1" stop-color="#0669bc" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="ac2a6fc2-ca48-4327-9a3c-d4dcc3256e15"
|
||||
x1="-1023.725"
|
||||
x2="-1029.98"
|
||||
y1="108.083"
|
||||
y2="105.968"
|
||||
gradientTransform="matrix(1 0 0 -1 1075 158)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stop-opacity=".3" />
|
||||
<stop offset=".071" stop-opacity=".2" />
|
||||
<stop offset=".321" stop-opacity=".1" />
|
||||
<stop offset=".623" stop-opacity=".05" />
|
||||
<stop offset="1" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="a7fee970-a784-4bb1-af8d-63d18e5f7db9"
|
||||
x1="-1027.165"
|
||||
x2="-997.482"
|
||||
y1="147.642"
|
||||
y2="68.561"
|
||||
gradientTransform="matrix(1 0 0 -1 1075 158)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stop-color="#3ccbf4" />
|
||||
<stop offset="1" stop-color="#2892df" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
fill="url(#e399c19f-b68f-429d-b176-18c2117ff73c)"
|
||||
d="M33.338 6.544h26.038l-27.03 80.087a4.152 4.152 0 0 1-3.933 2.824H8.149a4.145 4.145 0 0 1-3.928-5.47L29.404 9.368a4.152 4.152 0 0 1 3.934-2.825z"
|
||||
/>
|
||||
<path
|
||||
fill="#0078d4"
|
||||
d="M71.175 60.261h-41.29a1.911 1.911 0 0 0-1.305 3.309l26.532 24.764a4.171 4.171 0 0 0 2.846 1.121h23.38z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#ac2a6fc2-ca48-4327-9a3c-d4dcc3256e15)"
|
||||
d="M33.338 6.544a4.118 4.118 0 0 0-3.943 2.879L4.252 83.917a4.14 4.14 0 0 0 3.908 5.538h20.787a4.443 4.443 0 0 0 3.41-2.9l5.014-14.777 17.91 16.705a4.237 4.237 0 0 0 2.666.972H81.24L71.024 60.261l-29.781.007L59.47 6.544z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#a7fee970-a784-4bb1-af8d-63d18e5f7db9)"
|
||||
d="M66.595 9.364a4.145 4.145 0 0 0-3.928-2.82H33.648a4.146 4.146 0 0 1 3.928 2.82l25.184 74.62a4.146 4.146 0 0 1-3.928 5.472h29.02a4.146 4.146 0 0 0 3.927-5.472z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
Loading…
Reference in New Issue
Block a user