-
Notifications
You must be signed in to change notification settings - Fork 17
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
Jira integration for release readiness #682
Changes from 1 commit
7b12725
306ee1d
2ba7fbf
4ecc7bd
40abb72
9e2800a
dec657b
06afa23
3064452
cab68c6
0a2f845
f074834
d6b3ea8
77581ef
b6b54b5
7992163
771b569
1b76165
046c680
0f757b8
8ccbc63
85f5973
122f5b7
2b02710
c27544d
d0137e9
641d17c
94daa1a
463ca77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
class IntegrationListeners::JiraController < IntegrationListenerController | ||
using RefinedString | ||
|
||
INTEGRATION_CREATE_ERROR = "Failed to create the integration, please try again." | ||
|
||
def callback | ||
unless valid_state? | ||
redirect_to app_path(state_app), alert: INTEGRATION_CREATE_ERROR | ||
return | ||
end | ||
|
||
begin | ||
@integration = state_app.integrations.build(integration_params) | ||
@integration.providable = build_providable | ||
|
||
if @integration.providable.complete_access | ||
@integration.save! | ||
redirect_to app_path(state_app), | ||
notice: t("integrations.project_management.jira.integration_created") | ||
else | ||
@resources = @integration.providable.available_resources | ||
|
||
if @resources.blank? | ||
redirect_to app_integrations_path(state_app), | ||
alert: t("integrations.project_management.jira.no_organization") | ||
return | ||
end | ||
|
||
render "jira_integration/select_organization" | ||
end | ||
rescue => e | ||
Rails.logger.error("Failed to create Jira integration: #{e.message}") | ||
redirect_to app_integrations_path(state_app), | ||
alert: INTEGRATION_CREATE_ERROR | ||
end | ||
end | ||
|
||
def set_organization | ||
@integration = state_app.integrations.build(integration_params) | ||
@integration.providable = build_providable | ||
@integration.providable.cloud_id = params[:cloud_id] | ||
@integration.providable.code = params[:code] | ||
|
||
if @integration.save! | ||
@integration.providable.setup | ||
redirect_to app_path(@integration.integrable), | ||
notice: t("integrations.project_management.jira.integration_created") | ||
else | ||
@resources = @integration.providable.available_resources | ||
render "jira_integration/select_organization" | ||
end | ||
rescue => e | ||
Rails.logger.error("Failed to create Jira integration: #{e.message}") | ||
redirect_to app_integrations_path(state_app), | ||
alert: INTEGRATION_CREATE_ERROR | ||
end | ||
|
||
protected | ||
|
||
def providable_params | ||
super.merge( | ||
code: code, | ||
callback_url: callback_url | ||
) | ||
end | ||
|
||
private | ||
|
||
def callback_url | ||
host = request.host_with_port | ||
Rails.application.routes.url_helpers.jira_callback_url( | ||
host: host, | ||
protocol: request.protocol.gsub("://", "") | ||
kitallis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
end | ||
|
||
def state | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Notes to myself: add app config to the state, so that the redirect can show the app. |
||
@state ||= begin | ||
cleaned_state = params[:state].tr(" ", "+") | ||
JSON.parse(cleaned_state.decode).with_indifferent_access | ||
rescue ActiveSupport::MessageEncryptor::InvalidMessage => e | ||
Rails.logger.error "Invalid state parameter: #{e.message}" | ||
{} | ||
end | ||
end | ||
|
||
def error? | ||
params[:error].present? || state.empty? | ||
end | ||
|
||
def state_app | ||
@state_app ||= App.find(state[:app_id]) | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
|
||
export default class extends Controller { | ||
static targets = ["template", "container", "filter"] | ||
|
||
add(event) { | ||
event.preventDefault() | ||
const content = this.templateTarget.innerHTML.replace(/__INDEX__/g, this.filterTargets.length) | ||
this.containerTarget.insertAdjacentHTML("beforeend", content) | ||
} | ||
|
||
remove(event) { | ||
event.preventDefault() | ||
const filter = event.target.closest("[data-domain--release-filters-target='filter']") | ||
filter.remove() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export function toggleDisplay(target, condition) { | ||
if (target) { | ||
target.style.display = condition ? "block" : "none"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
kitallis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import { toggleDisplay } from "./helper" | ||
|
||
export default class extends Controller { | ||
static targets = [ "projectCheckbox", "config", "filterDivider"] | ||
|
||
connect() { | ||
this.toggleConfigurations() | ||
this.toggleFilterDivider() | ||
} | ||
|
||
toggle() { | ||
this.toggleConfigurations() | ||
this.toggleFilterDivider() | ||
} | ||
|
||
toggleConfigurations() { | ||
const configs = document.querySelectorAll('.project-config') | ||
configs.forEach(config => { | ||
const projectKey = config.dataset.project | ||
const checkbox = document.querySelector(`#project_${projectKey}`) | ||
if (checkbox) { | ||
toggleDisplay(config, checkbox.checked) | ||
} | ||
}) | ||
} | ||
|
||
toggleFilterDivider() { | ||
const anyProjectSelected = this.projectCheckboxTargets.some(checkbox => checkbox.checked) | ||
|
||
if (this.hasFilterDividerTarget) { | ||
toggleDisplay(this.filterDividerTarget, anyProjectSelected) | ||
this.filterDividerTarget.classList.add('border-t') | ||
this.filterDividerTarget.classList.add('border-b') | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { Controller } from "@hotwired/stimulus" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Notes to myself: this controller shouldn't be project states, it should be more general than that, perhaps just even content toggle or something, can I use reveal here? |
||
import { toggleDisplay } from "./helper" | ||
|
||
export default class extends Controller { | ||
static targets = ["content"] | ||
|
||
toggle(event) { | ||
toggleDisplay(this.contentTarget, event.target.checked) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: what scenario is this? afaik we select organization in the jira redirect itself, so why would we need to select again inside Tramline?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Atlassian API endpoint https://api.atlassian.com/oauth/token/accessible-resources returns a list of all resources (organizations) the user has access to after authentication.
This is because:
Multiple Resources: The API does not automatically limit the response to the organization selected during the redirect. Instead, it provides all accessible organizations for the user.
User Selection in Tramline: To ensure the correct organization is used in Tramline, the app must present the user with the list of accessible organizations returned by the API. The user then selects the desired organization explicitly within the app.