diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index bfac755..a54922b 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -36,7 +36,7 @@ jobs: run: sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' coverage.xml - name: SonarCloud Scan - uses: sonarsource/sonarqube-scan-action@v7.0.0 + uses: sonarsource/sonarqube-scan-action@v8.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} if: env.SONAR_TOKEN != '' diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index ec68a81..99e3014 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -51,7 +51,7 @@ jobs: run: sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' coverage.xml - name: SonarCloud Scan - uses: sonarsource/sonarqube-scan-action@v7.0.0 + uses: sonarsource/sonarqube-scan-action@v8.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} if: env.SONAR_TOKEN != '' diff --git a/.vscode/settings.json b/.vscode/settings.json index bcf50f0..0443453 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,5 +8,22 @@ ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, - "python.envFile": "${workspaceFolder}/.test.env" + "pylint.ignorePatterns": ["**/tests/**"], + "pylint.args": [ + // broad-exception-caught + "--disable=W0718", + // missing-module-docstring + "--disable=C0114", + // missing-class-docstring + "--disable=C0115", + // too-few-public-methods + "--disable=R0903", + // line-too-long + "--disable=C0301", + // abstract-method + "--disable=W0223", + ], + "python.envFile": "${workspaceFolder}/.test.env", + "python-envs.defaultEnvManager": "ms-python.python:pipenv", + "files.trimTrailingWhitespace": true } \ No newline at end of file diff --git a/Pipfile b/Pipfile index 0c6c053..47a5f13 100644 --- a/Pipfile +++ b/Pipfile @@ -6,7 +6,7 @@ name = "pypi" [packages] slack-bolt = "==1.28.0" python-dotenv = "==1.2.2" -gunicorn = "==25.3.0" +gunicorn = "==26.0.0" flask = "==3.1.3" requests = "==2.33.1" pyjwt = "==2.12.1" diff --git a/Pipfile.lock b/Pipfile.lock index 1beae51..b3d0e8b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4d90a0e77224b2f9f44cbc14ccca6cbf199c427b03c67cc297ff8b7ed084b365" + "sha256": "ecee4d8c4f005341cc1a63c136c464ab440e4b5eb83f28d87c47cce604667b9c" }, "pipfile-spec": 6, "requires": { @@ -229,12 +229,12 @@ }, "gunicorn": { "hashes": [ - "sha256:cacea387dab08cd6776501621c295a904fe8e3b7aae9a1a3cbb26f4e7ed54660", - "sha256:f74e1b2f9f76f6cd1ca01198968bd2dd65830edc24b6e8e4d78de8320e2fe889" + "sha256:40233d26a5f0d1872916188c276e21641155111c2853f0c2cd55260aec0d24fc", + "sha256:ca9346f85e3a4aeeb64d491045c16b9a35647abd37ea15efe53080eb8b090baf" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==25.3.0" + "version": "==26.0.0" }, "idna": { "hashes": [ diff --git a/src/app.py b/src/app.py index 1d33aea..4709e94 100644 --- a/src/app.py +++ b/src/app.py @@ -10,8 +10,8 @@ # Register App Events request_change_handler.register_events(app) -# Register App APIs +# Register App APIs slack_app = slack_handler.register_handler(app) if __name__ == "__main__": - slack_app.run() \ No newline at end of file + slack_app.run() diff --git a/src/controller/change_request.py b/src/controller/change_request.py index f856372..04b8ad2 100644 --- a/src/controller/change_request.py +++ b/src/controller/change_request.py @@ -6,7 +6,7 @@ populate_selection, populate_metadata, populate_selection_status, - prepare_body, + prepare_body, get_status, get_state_value, get_state_name, @@ -33,7 +33,7 @@ def on_domain_selected(ack, body, client, logger): team_id = body["team"]["id"] domain_id = get_selected_action(body) domain_name = get_selected_action_text(body) - + envs = SwitcherService().get_environments(team_id, domain_id or "") or [] # Clear previous selection @@ -48,7 +48,7 @@ def on_domain_selected(ack, body, client, logger): values = get_environment_keyval(envs) ) - populate_metadata(body["view"], { + populate_metadata(body["view"], { "domain_id": domain_id, "domain_name": domain_name }) @@ -94,7 +94,7 @@ def on_environment_selected(ack, body, client, logger): item = "Group", values = get_keyval("name", groups) ) - + # Push changes to view view_hash = body["view"]["hash"] view_id = body["view"]["id"] @@ -225,18 +225,18 @@ def on_change_request_review(ack, body, client, view, logger): user_message = ":large_green_square: *Request does not require approval*: Updated with success!" elif result == 'FROZEN_ENVIRONMENT': user_message = ":large_red_square: *Request cannot be made*: Environment is frozen." - + if user_message is not None: client.chat_postMessage( channel = user["id"], text = "Change Request Review", blocks = create_block_message(user_message) ) - + return view, user_message except Exception as e: client.chat_postMessage( - channel = user["id"], + channel = user["id"], text = f"There was an error with your request: {e}" ) @@ -250,7 +250,7 @@ def on_submit(ack, body, client, logger): ack() user = body["user"] team_id = body["team"]["id"] - + try: # Collect args observation = get_state_value(body["view"], "selection_observation") @@ -286,10 +286,11 @@ def on_submit(ack, body, client, logger): except Exception as e: logger.error(f"Error on submitting: {e}") client.chat_postMessage( - channel = user["id"], + channel = user["id"], text = f"There was an error with your request: {e}" ) - + return e + def on_change_request_abort(ack, body, client): """ Return to home view """ @@ -303,7 +304,7 @@ def on_change_request_abort(ack, body, client): def on_request_approved(ack, body, client, logger): """ Approve ticket through Switcher API and update chat message """ - + ack() message_ts = body["message"]["ts"] team_id = body["team"]["id"] @@ -335,10 +336,11 @@ def on_request_approved(ack, body, client, logger): ts = message_ts, blocks = create_block_message(f":large_yellow_square: *{e.args[0]}*") ) + return e def on_request_denied(ack, body, client, logger): """ Deny ticket through Switcher API and update chat message """ - + ack() message_ts = body["message"]["ts"] team_id = body["team"]["id"] @@ -360,7 +362,7 @@ def on_request_denied(ack, body, client, logger): ts = message_ts, blocks = message_blocks ) - + return message_blocks except Exception as e: logger.error(f"Error on denying: {e}") @@ -369,4 +371,5 @@ def on_request_denied(ack, body, client, logger): text = e.args[0], ts = message_ts, blocks = create_block_message(f":large_yellow_square: *{e.args[0]}*") - ) \ No newline at end of file + ) + return e diff --git a/src/controller/home.py b/src/controller/home.py index 7e57eb0..8f50934 100644 --- a/src/controller/home.py +++ b/src/controller/home.py @@ -44,4 +44,5 @@ def on_change_request_opened(ack, body, client, logger): return change_request except Exception as e: - logger.error(f"Error opening change request form: {e}") \ No newline at end of file + logger.error(f"Error opening change request form: {e}") + return None diff --git a/src/controller/slack_app.py b/src/controller/slack_app.py index a8fd8ca..e0f289b 100644 --- a/src/controller/slack_app.py +++ b/src/controller/slack_app.py @@ -7,15 +7,15 @@ from slack_bolt.adapter.flask import SlackRequestHandler from slack_sdk.oauth.state_store import FileOAuthStateStore -from flask import Flask, request, session, make_response +from flask import Flask, request from utils.constants import SWITCHER_URL, SWITCHER_API_URL, VERSION from store.switcher_store import SwitcherAppInstallationStore - + class SlackAppHandler: def build_app(self, api_url: str = os.environ.get("SWITCHER_API_URL") or "") -> App: """ Build the Slack App Settings """ - + return App( signing_secret = os.environ.get("SLACK_SIGNING_SECRET"), installation_store = SwitcherAppInstallationStore(api_url), @@ -66,12 +66,10 @@ def oauth_redirect(): return flask_app def success(self, args: SuccessArgs) -> BoltResponse: + """ Handles the redirection from Slack's OAuth flow when the installation is successful """ assert args.request is not None - t_id = args.installation.team_id - e_id = args.installation.enterprise_id - - if e_id is None: e_id = "" - if t_id is None: t_id = "" + t_id = args.installation.team_id if args.installation.team_id is not None else "" + e_id = args.installation.enterprise_id if args.installation.enterprise_id is not None else "" return BoltResponse( status = 308, @@ -81,6 +79,7 @@ def success(self, args: SuccessArgs) -> BoltResponse: ) def failure(self, args: FailureArgs) -> BoltResponse: + """ Handles the redirection from Slack's OAuth flow when the installation fails """ assert args.request is not None assert args.reason is not None @@ -89,4 +88,4 @@ def failure(self, args: FailureArgs) -> BoltResponse: headers = { "Location": f"{SWITCHER_URL}/slack/authorization?error=1&reason={args.reason}", } - ) \ No newline at end of file + ) diff --git a/src/errors/__init__.py b/src/errors/__init__.py index 8822ef0..7c17524 100644 --- a/src/errors/__init__.py +++ b/src/errors/__init__.py @@ -7,17 +7,17 @@ class SwitcherContextError(SwitcherSlackAppError): def __init__(self, missing: list[str]): attributes = " - ".join([str(elem) for elem in missing]) msg = f"Missing [{attributes}]" - super(SwitcherContextError, self).__init__(msg) + super().__init__(msg) class SwitcherValidationError(SwitcherSlackAppError): """ Error raised when ticket has issues and cannot be opened """ def __init__(self, message: str): message = "Try it again later" if message is None else message - super(SwitcherValidationError, self).__init__(message) + super().__init__(message) class SwitcherSlackInstallationError(SwitcherSlackAppError): """ Error raised when slack installation fails """ def __init__(self, message: str): - super(SwitcherSlackInstallationError, self).__init__(message) \ No newline at end of file + super().__init__(message) diff --git a/src/events/request_change.py b/src/events/request_change.py index 70b9087..59b2e00 100644 --- a/src/events/request_change.py +++ b/src/events/request_change.py @@ -15,7 +15,7 @@ class RequestChangeEventHandler: def register_events(self, app): """ Register events for the Request Change feature """ - # Show the app landing page + # Show the app landing page @app.event("app_home_opened") def app_home_opened(client, event, logger): on_home_opened(client, event, logger) @@ -34,20 +34,20 @@ def selection_domain(ack, body, client, logger): @app.action("selection_environment") def selection_environment(ack, body, client, logger): on_environment_selected(ack, body, client, logger) - + # Update Change Request modal with available group switchers @app.action("selection_group") - def selection_group(ack, body, client, view, logger): + def selection_group(ack, body, client, logger): on_group_selected(ack, body, client, logger) # Update Change Request modal with status options @app.action("selection_switcher") - def selection_switcher(ack, body, client, logger): + def selection_switcher(ack, body, client): on_switcher_selected(ack, body, client) # Confirm status selection @app.action("selection_status") - def selection_status(ack, body, client, logger): + def selection_status(ack): ack() # Submit Change Request for review @@ -57,12 +57,12 @@ def handle_change_request_review(ack, body, client, view, logger): # Submit Change Request for verification and approval @app.action("change_request_submit") - def handle_submission(ack, body, client, view, logger): + def handle_submission(ack, body, client, logger): on_submit(ack, body, client, logger) # Abort Change Request @app.action("change_request_abort") - def handle_change_request_abort(ack, body, client, view, logger): + def handle_change_request_abort(ack, body, client): on_change_request_abort(ack, body, client) # Request approved @@ -73,4 +73,4 @@ def request_approved(ack, body, client, logger): # Request denied @app.action("request_denied") def request_denied(ack, body, client, logger): - on_request_denied(ack, body, client, logger) \ No newline at end of file + on_request_denied(ack, body, client, logger) diff --git a/src/payloads/change_request.py b/src/payloads/change_request.py index 75c37ce..8f9b1fd 100644 --- a/src/payloads/change_request.py +++ b/src/payloads/change_request.py @@ -10,144 +10,149 @@ NEW_SELECTION = [{ "name": "-", "value": "-" }] REQUEST_REVIEW = { - "type": "home", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": "Change Request Review" - } - }, - { - "type": "section", - "text": { - "type": "plain_text", - "text": "Please, review your request and submit it for approval." - } - }, - { - "type": "divider" - }, - { - "type": "input", - "element": { - "type": "plain_text_input", - "multiline": bool(True), - "action_id": "selection_observation" - }, - "label": { - "type": "plain_text", - "text": "Observations" - } - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Submit" - }, - "style": "primary", - "action_id": "change_request_submit" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Cancel" - }, - "style": "danger", - "action_id": "change_request_abort" - } - ] - } - ] + "type": "home", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "Change Request Review" + } + }, + { + "type": "section", + "text": { + "type": "plain_text", + "text": "Please, review your request and submit it for approval." + } + }, + { + "type": "divider" + }, + { + "type": "input", + "element": { + "type": "plain_text_input", + "multiline": bool(True), + "action_id": "selection_observation" + }, + "label": { + "type": "plain_text", + "text": "Observations" + } + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Submit" + }, + "style": "primary", + "action_id": "change_request_submit" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Cancel" + }, + "style": "danger", + "action_id": "change_request_abort" + } + ] + } + ] } def create_block_message(text): - return [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": text - } - }, - { - "type": "divider" - } - ] + """ Creates a block message with the given text. """ + return [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": text + } + }, + { + "type": "divider" + } + ] def get_request_message(ticket_payload, context): - message = [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "The following request has been opened for approval" - } - }, - { - "type": "divider" - }, - { - "type": "section", - "fields": [] - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Approve" - }, - "style": "primary", - "value": ticket_payload, - "action_id": "request_approved" - }, - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Deny" - }, - "style": "danger", - "value": ticket_payload, - "action_id": "request_denied" - } - ] - } - ] + """ Creates a block message for the given ticket payload and context. """ + message = [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "The following request has been opened for approval" + } + }, + { + "type": "divider" + }, + { + "type": "section", + "fields": [] + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Approve" + }, + "style": "primary", + "value": ticket_payload, + "action_id": "request_approved" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Deny" + }, + "style": "danger", + "value": ticket_payload, + "action_id": "request_denied" + } + ] + } + ] - add_field(message[2]["fields"], "Domain", context["domain_name"]) - add_field(message[2]["fields"], "Environment", context["environment"]) - add_field(message[2]["fields"], "Group", context["group"]) - if context["switcher"] is not None: - add_field(message[2]["fields"], "Switcher", context["switcher"]) + add_field(message[2]["fields"], "Domain", context["domain_name"]) + add_field(message[2]["fields"], "Environment", context["environment"]) + add_field(message[2]["fields"], "Group", context["group"]) + if context["switcher"] is not None: + add_field(message[2]["fields"], "Switcher", context["switcher"]) - add_field(message[2]["fields"], "Status", "Enable" if context["status"] == "true" else "Disable") - add_field(message[2]["fields"], "Observations", context.get("observations", "")) - return message + add_field(message[2]["fields"], "Status", "Enable" + if context["status"] == "true" else "Disable") + add_field(message[2]["fields"], "Observations", context.get("observations", "")) + return message def create_request_review(context): - view = copy.deepcopy(REQUEST_REVIEW) - insert_summary(view["blocks"], 3, "Domain", context["domain_name"]) - insert_summary(view["blocks"], 4, "Environment", context["environment"]) - insert_summary(view["blocks"], 5, "Group", context["group"]) + """ Creates a request review view for the given context. """ + view = copy.deepcopy(REQUEST_REVIEW) + insert_summary(view["blocks"], 3, "Domain", context["domain_name"]) + insert_summary(view["blocks"], 4, "Environment", context["environment"]) + insert_summary(view["blocks"], 5, "Group", context["group"]) - status = "Enable" if context["status"] == "true" else "Disable" - if context["switcher"] is not None: - insert_summary(view["blocks"], 6, "Switcher", context["switcher"]) - insert_summary_value(view["blocks"], 7, status) - else: - insert_summary_value(view["blocks"], 6, status) - - return view + status = "Enable" if context["status"] == "true" else "Disable" + if context["switcher"] is not None: + insert_summary(view["blocks"], 6, "Switcher", context["switcher"]) + insert_summary_value(view["blocks"], 7, status) + else: + insert_summary_value(view["blocks"], 6, status) + + return view def read_request_metadata(view): - return json.loads(view.get("private_metadata", {})) + """ Reads the request metadata from the given view. """ + return json.loads(view.get("private_metadata", {})) diff --git a/src/payloads/home.py b/src/payloads/home.py index c88eb1f..4b22fdd 100644 --- a/src/payloads/home.py +++ b/src/payloads/home.py @@ -177,4 +177,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/src/services/switcher_client.py b/src/services/switcher_client.py index 447b389..5914867 100644 --- a/src/services/switcher_client.py +++ b/src/services/switcher_client.py @@ -20,39 +20,46 @@ def __init__(self, api_url: str): self._api_url = api_url def do_post(self, path: str, body: Optional[dict]) -> Response: + """ do_post sends a POST request to the Switcher API with the given path and body """ return self.__response_handler__(requests.post( **self.__request_builder__( url = self._api_url + path, resource = path ), json = body, + timeout = 5, verify = self.__cert_path ) ) def do_get(self, path: str, params: Optional[dict]) -> Response: + """ do_get sends a GET request to the Switcher API with the given path and query parameters """ return self.__response_handler__(requests.get( **self.__request_builder__( url = self._api_url + path, resource = path ), params = params, + timeout = 5, verify = self.__cert_path ) ) def do_delete(self, path: str, params: Optional[dict]) -> Response: + """ do_delete sends a DELETE request to the Switcher API with the given path and query parameters """ return self.__response_handler__(requests.delete( **self.__request_builder__( url = self._api_url + path, resource = path ), params = params, + timeout = 5, verify = self.__cert_path ) ) def do_graphql(self, query_request: str) -> dict: + """ do_graphql sends a GraphQL request to the Switcher API with the given query string """ client = self.__gql_client__("slack-graphql") query = gql(query_request) return client.execute(query) @@ -71,7 +78,7 @@ def __generate_token__(self, resource: str) -> str: def __request_builder__(self, url: str, resource: str) -> dict: return { "url": url, - "headers": { + "headers": { "Authorization": f"Bearer {self.__generate_token__(resource)}" } } @@ -96,4 +103,4 @@ def __gql_client__(self, resource: str) -> Client: retries = 3, verify = self.__cert_path or "" ) - ) \ No newline at end of file + ) diff --git a/src/services/switcher_service.py b/src/services/switcher_service.py index b93da3e..42b9365 100644 --- a/src/services/switcher_service.py +++ b/src/services/switcher_service.py @@ -10,24 +10,26 @@ class SwitcherService(SwitcherClient): def __init__(self, *, api_url: Optional[str] = None): SwitcherClient.__init__( - self, + self, api_url or os.environ.get("SWITCHER_API_URL") or "" ) def get_domains(self, team_id: str) -> list[dict]: + """ Fetch all Domains linked to the Team """ response = self.do_get( path = "/slack/v1/domains", params = { "team_id": team_id } ) - + if response.status_code != 200: raise SwitcherValidationError("Error fetching domains") - + return json.loads(response.data.decode('UTF-8')) def get_environments(self, team_id: str, domain_id: str) -> list[str] | None: + """ Fetch all Environments linked to the Domain """ response: dict = self.do_graphql(f''' query {{ configuration( @@ -46,12 +48,13 @@ def get_environments(self, team_id: str, domain_id: str) -> list[str] | None: return environments def get_groups(self, team_id: str, domain_id: str, environment: str) -> list[dict] | None: + """ Fetch all Groups linked to the Environment """ response: dict = self.do_graphql(f''' query {{ configuration( - slack_team_id: "{team_id}", + slack_team_id: "{team_id}", domain: "{domain_id}", - environment: "{environment}") + environment: "{environment}") {{ group {{ name @@ -68,13 +71,14 @@ def get_groups(self, team_id: str, domain_id: str, environment: str) -> list[dic return groups def get_switchers(self, team_id: str, domain_id: str, environment: str, group: str) -> list[dict] | None: + """ Fetch all Switchers linked to the Group """ response: dict = self.do_graphql(f''' query {{ configuration( slack_team_id: "{team_id}", domain: "{domain_id}", environment: "{environment}", - group: "{group}") + group: "{group}") {{ config {{ key @@ -92,7 +96,7 @@ def get_switchers(self, team_id: str, domain_id: str, environment: str, group: s def validate_ticket(self, team_id: str, context: dict) -> str: """ Validates if Ticket content is valid """ - + response = self.do_post( path = "/slack/v1/ticket/validate", body = { @@ -110,7 +114,7 @@ def validate_ticket(self, team_id: str, context: dict) -> str: if response.status_code != 200: data = json.loads(response.data.decode('UTF-8')) raise SwitcherValidationError(data.get("error")) - + data = json.loads(response.data.decode('UTF-8')) return data.get("result") @@ -149,10 +153,10 @@ def deny_request(self, team_id: str, domain_id: str, ticket_id: str): """ Dispatch change request denied """ self.__process_request__(team_id, domain_id, ticket_id, False) - def __process_request__(self, - team_id: str, + def __process_request__(self, + team_id: str, domain_id: str, - ticket_id: str, + ticket_id: str, approved: bool ) -> str: """ Dispatch change request approval action """ @@ -171,4 +175,4 @@ def __process_request__(self, if response.status_code != 200: raise SwitcherValidationError(data.get("error")) - return data.get("message") \ No newline at end of file + return data.get("message") diff --git a/src/services/switcher_store.py b/src/services/switcher_store.py index aecc9f9..3b5530d 100644 --- a/src/services/switcher_store.py +++ b/src/services/switcher_store.py @@ -12,11 +12,12 @@ def __init__(self, api_url: str): def save_installation( self, enterprise_id: str, - team_id: str, - user_id: str, + team_id: str, + user_id: str, installation_payload: dict, bot_payload: dict ) -> Response: + """ Save the installation payload received from Slack during the app installation process and link it to the corresponding Domain """ response = self.do_post( path = "/slack/v1/installation", body = { @@ -27,17 +28,18 @@ def save_installation( "bot_payload": bot_payload } ) - + if response.status_code != 201: raise SwitcherSlackInstallationError(response.data) return response def find_bot( - self, - enterprise_id: Optional[str], + self, + enterprise_id: Optional[str], team_id: Optional[str] ) -> Response: + """ Find the bot linked to the Team and Enterprise IDs received from Slack during the app installation process """ return self.do_get( path = "/slack/v1/findbot", params = { @@ -47,10 +49,11 @@ def find_bot( ) def find_installation( - self, - enterprise_id: Optional[str], + self, + enterprise_id: Optional[str], team_id: Optional[str] ) -> Response: + """ Find the installation linked to the Team and Enterprise IDs received from Slack during the app installation process """ return self.do_get( path = "/slack/v1/findinstallation", params = { @@ -58,13 +61,14 @@ def find_installation( "team_id": team_id } ) - + def delete_installation( - self, - enterprise_id: Optional[str], + self, + enterprise_id: Optional[str], team_id: Optional[str], user_id: Optional[str] = None ) -> Response: + """ Delete the installation linked to the Team and Enterprise IDs received from Slack during the app uninstallation process """ return self.do_delete( path = "/slack/v1/installation", params = { @@ -72,4 +76,4 @@ def delete_installation( "team_id": team_id, "user_id": user_id } - ) \ No newline at end of file + ) diff --git a/src/store/switcher_store.py b/src/store/switcher_store.py index a9fc250..2ffb3f7 100644 --- a/src/store/switcher_store.py +++ b/src/store/switcher_store.py @@ -44,7 +44,7 @@ def save(self, installation: Installation): message = \ "Failed to save installation data for enterprise:" \ f"{e_id}, team: {t_id}: {e}" - + self.logger.warning(message) async def async_find_bot( @@ -73,14 +73,14 @@ def find_bot( enterprise_id = enterprise_id, team_id = team_id ) - + bot_payload = json.loads(response.data) return Bot(**bot_payload) except Exception as e: message = \ "Failed to find bot installation data for enterprise:" \ f"{enterprise_id}, team: {team_id}: {e}" - + self.logger.warning(message) return None @@ -120,7 +120,7 @@ def find_installation( message = \ "Failed to find an installation data for enterprise:" \ f"{enterprise_id}, team: {team_id}: {e}" - + self.logger.warning(message) return None @@ -130,9 +130,9 @@ async def async_delete_bot( return self.delete_bot(enterprise_id = enterprise_id, team_id = team_id) def delete_bot( - self, - *, - enterprise_id: Optional[str], + self, + *, + enterprise_id: Optional[str], team_id: Optional[str] ) -> None: enterprise_id, team_id = self._input_sanitize(enterprise_id, team_id) @@ -163,13 +163,13 @@ def delete_installation( self._store_service.delete_installation( enterprise_id = enterprise_id, team_id = team_id, - user_id = user_id + user_id = user_id ) except Exception as e: message = \ "Failed to delete installation data for enterprise:" \ f"{enterprise_id}, team: {team_id}: {e}" - + self.logger.warning(message) @staticmethod @@ -183,4 +183,4 @@ def _input_sanitize( if team_id is None: team_id = "" - return enterprise_id, team_id \ No newline at end of file + return enterprise_id, team_id diff --git a/src/utils/constants.py b/src/utils/constants.py index 15dc272..dc83d7e 100644 --- a/src/utils/constants.py +++ b/src/utils/constants.py @@ -7,4 +7,4 @@ SWITCHER_URL = os.environ.get("SWITCHER_URL") SWITCHER_API_URL = os.environ.get("SWITCHER_API_URL") RELEASE_TIME = os.environ.get("RELEASE_TIME", "latest") -VERSION = f"2.1.1 {RELEASE_TIME}" \ No newline at end of file +VERSION = f"2.1.1 {RELEASE_TIME}" diff --git a/src/utils/slack_payload_util.py b/src/utils/slack_payload_util.py index 4a05d5d..e4bc6d7 100644 --- a/src/utils/slack_payload_util.py +++ b/src/utils/slack_payload_util.py @@ -19,6 +19,7 @@ def populate_selection(body, item, values): "value": value["value"] }) return block + return None def populate_selection_status(view, status): """ Includes Status selection block """ @@ -60,6 +61,7 @@ def insert_summary_value(block, index, value): }) def add_field(fields, label, value): + """ Add field to summary block """ fields.append({ "type": "mrkdwn", "text": f"*{label}:*\n{value}" @@ -86,38 +88,49 @@ def get_state_value(view, option): def get_state_name(view, option): """ Get selected values stored at the state element """ + value = None element_name = "" for element in view["state"]["values"]: element_name = view["state"]["values"][element].get(option, None) if element_name is not None: - return element_name.get("selected_option", None).get("text", None).get("text", None) + value = element_name.get("selected_option", None).get("text", None).get("text", None) + break + return value def __get_state(key, view, option) -> str | None: """ Get selected state element """ + value = None element_value = "" for element in view["state"]["values"]: element_value = view["state"]["values"][element].get(option, None) if element_value is not None: if element_value.get("selected_option", None) is not None and element_value["selected_option"].get(key, "-") != "-": - return element_value["selected_option"][key] - return element_value.get(key, None) + value = element_value["selected_option"][key] + break + value = element_value.get(key, None) + break + return value def get_selected_action(body): """ Get selected value from an action event """ - + + value = None if body["actions"] is not None and len(body["actions"]) > 0: - return body["actions"][0]["selected_option"]["value"] + value = body["actions"][0]["selected_option"]["value"] + return value def get_selected_action_text(body): """ Get selected value from an action event """ - + + value = None if body["actions"] is not None and len(body["actions"]) > 0: - return body["actions"][0]["selected_option"]["text"]["text"] + value = body["actions"][0]["selected_option"]["text"]["text"] + return value def get_selected_action_status(body): """ Get selected value from an action event """ - + selected_action_text = get_selected_action_text(body) return get_status(selected_action_text) @@ -130,4 +143,4 @@ def __generate_block_id(prefix): """ Generate block id """ time = datetime.now() - return f"{prefix}_{time}" \ No newline at end of file + return f"{prefix}_{time}" diff --git a/src/utils/switcher_util.py b/src/utils/switcher_util.py index 1ceab4f..b0226fe 100644 --- a/src/utils/switcher_util.py +++ b/src/utils/switcher_util.py @@ -30,14 +30,14 @@ def get_keyval_of_keys(keys: list[str], values: list[dict]) -> list[dict]: def validate_context_request(context: dict): """ Validates if context input is consistent """ - + missing = [] if context.get("domain_name", None) is None: missing.append("Domain") if context.get("domain_id", None) is None: missing.append("Domain ID") - + if context.get("environment", None) is None: missing.append("Environment") @@ -46,6 +46,6 @@ def validate_context_request(context: dict): if context.get("status", None) is None: missing.append("Status") - - if len(missing): - raise SwitcherContextError(missing) \ No newline at end of file + + if missing: + raise SwitcherContextError(missing)