From 8c58c0866ddaa837808564fe3a4d92be6af45eed Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Tue, 4 Jun 2024 13:58:42 -0700 Subject: [PATCH 01/11] :hammer: Add command to create branch name from Jira issue url. --- bin/dev | 18 +++++++++++++++++- bin/jira_api.rb | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 bin/jira_api.rb diff --git a/bin/dev b/bin/dev index a0dce7f2..073c96df 100755 --- a/bin/dev +++ b/bin/dev @@ -1,9 +1,12 @@ #!/usr/bin/env ruby +require_relative './jira_api' + class DevHelper # Support dashes in command names COMMAND_TO_METHOD = { - "ts-node" => :ts_node + "ts-node" => :ts_node, + "branch-from" => :branch_from, } METHOD_TO_COMMAND = COMMAND_TO_METHOD.invert @@ -173,6 +176,18 @@ class DevHelper end end + ## + # Generates and checks out a new branch based on the Jira issue URL. + # Example: + # dev branch-from https://yg-hpw.atlassian.net/browse/ELCC-61 + # + # Produces: + # git checkout -b elcc-61/add-wage-enhancement-replication-across-months + def branch_from(jira_issue_url, *args, **kwargs) + branch_name = JiraApi.build_branch_name(jira_issue_url) + system("git checkout -b #{branch_name}") + end + def bash_completions completions = public_methods(false).reject { |word| %i[call].include?(word) @@ -203,6 +218,7 @@ class DevHelper def take_over_needed?(file_or_directory) files_owned_by_others = system("find #{file_or_directory} -not -user #{user_id} -print -quit | grep -q .") + files_owned_by_others end def user_id diff --git a/bin/jira_api.rb b/bin/jira_api.rb new file mode 100644 index 00000000..77493dcb --- /dev/null +++ b/bin/jira_api.rb @@ -0,0 +1,44 @@ +require 'net/http' +require 'json' +require 'uri' + +class JiraApi + JIRA_USERNAME = ENV['JIRA_USERNAME'] + JIRA_API_TOKEN = ENV['JIRA_API_TOKEN'] + JIRA_SITE = 'https://yg-hpw.atlassian.net' + + def self.build_branch_name(jira_ticket_url) + if JIRA_USERNAME.nil? || JIRA_API_TOKEN.nil? + puts 'Please set JIRA_USERNAME and JIRA_API_TOKEN environment variables' + return + end + + issue_key = extract_issue_key(jira_ticket_url) + issue_title = fetch_issue_title(issue_key) + format_branch_name(issue_key, issue_title) + end + + private + + def self.extract_issue_key(url) + url.match(%r{/browse/([A-Z]+-\d+)})[1] + end + + def self.fetch_issue_title(issue_key) + uri = URI("#{JIRA_SITE}/rest/api/3/issue/#{issue_key}") + request = Net::HTTP::Get.new(uri) + request.basic_auth(JIRA_USERNAME, JIRA_API_TOKEN) + + response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| + http.request(request) + end + + data = JSON.parse(response.body) + data['fields']['summary'] + end + + def self.format_branch_name(issue_key, issue_title) + formatted_title = issue_title.downcase.gsub(/\s+/, '-').gsub(/[^a-z0-9\-]/, '') + "#{issue_key.downcase}/#{formatted_title}" + end +end From 96e48f8a311a9e2d622db57b6472c86a5280e43f Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Tue, 4 Jun 2024 15:48:10 -0700 Subject: [PATCH 02/11] :construction: Add wage enhancement replication api wiring. Service is not yet implemented. --- api/src/controllers/index.ts | 1 + .../controllers/wage-enhancements/index.ts | 1 + .../replicate-estimates-controller.ts | 34 +++++++++++++++ api/src/routes/api-router.ts | 4 ++ api/src/services/wage-enhancements/index.ts | 1 + .../replicate-estimates-service.ts | 16 +++++++ web/src/api/wage-enhancements-api.ts | 6 +++ ...DashboardEmployeesMonthlyWorksheetPage.vue | 42 ++++++++++++++++++- 8 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 api/src/controllers/wage-enhancements/index.ts create mode 100644 api/src/controllers/wage-enhancements/replicate-estimates-controller.ts create mode 100644 api/src/services/wage-enhancements/index.ts create mode 100644 api/src/services/wage-enhancements/replicate-estimates-service.ts diff --git a/api/src/controllers/index.ts b/api/src/controllers/index.ts index 52508cd1..9f507050 100644 --- a/api/src/controllers/index.ts +++ b/api/src/controllers/index.ts @@ -8,3 +8,4 @@ export { WageEnhancementsController } from "./wage-enhancements-controller" // bundled exports export * as FundingSubmissionLineJsons from "./funding-submission-line-jsons" +export * as WageEnhancements from "./wage-enhancements" diff --git a/api/src/controllers/wage-enhancements/index.ts b/api/src/controllers/wage-enhancements/index.ts new file mode 100644 index 00000000..971f9b29 --- /dev/null +++ b/api/src/controllers/wage-enhancements/index.ts @@ -0,0 +1 @@ +export { ReplicateEstimatesController } from "./replicate-estimates-controller" diff --git a/api/src/controllers/wage-enhancements/replicate-estimates-controller.ts b/api/src/controllers/wage-enhancements/replicate-estimates-controller.ts new file mode 100644 index 00000000..fc6b425b --- /dev/null +++ b/api/src/controllers/wage-enhancements/replicate-estimates-controller.ts @@ -0,0 +1,34 @@ +import { isNaN } from "lodash" + +import { ReplicateEstimatesService } from "@/services/wage-enhancements" +import BaseController from "@/controllers/base-controller" + +export class ReplicateEstimatesController extends BaseController { + async create() { + try { + const centreId = parseInt((this.query.centreId as string) ?? this.request.body.centreId) + const fiscalPeriodId = parseInt( + (this.query.fiscalPeriodId as string) ?? this.request.body.fiscalPeriodId + ) + if (isNaN(centreId)) { + return this.response.status(422).json({ + message: "centreId not provided or invalid", + }) + } + + if (isNaN(fiscalPeriodId)) { + return this.response.status(422).json({ + message: "fiscalPeriodId not provided or invalid", + }) + } + + await ReplicateEstimatesService.perform(centreId, fiscalPeriodId) + } catch (error) { + return this.response.status(422).json({ + message: `Error replicating wage enhancement estimates: ${error}`, + }) + } + } +} + +export default ReplicateEstimatesController diff --git a/api/src/routes/api-router.ts b/api/src/routes/api-router.ts index 5260913a..aa17e5d6 100644 --- a/api/src/routes/api-router.ts +++ b/api/src/routes/api-router.ts @@ -17,6 +17,7 @@ import { FundingSubmissionLineJsons, FundingSubmissionLineJsonsController, PaymentsController, + WageEnhancements, WageEnhancementsController, } from "@/controllers" @@ -71,6 +72,9 @@ apiRouter .get(WageEnhancementsController.show) .patch(WageEnhancementsController.update) .delete(WageEnhancementsController.destroy) +apiRouter + .route("/api/wage-enhancements/replicate-estimates") + .post(WageEnhancements.ReplicateEstimatesController.create) // if no other routes match, return a 404 apiRouter.use("/api", (req: Request, res: Response) => { diff --git a/api/src/services/wage-enhancements/index.ts b/api/src/services/wage-enhancements/index.ts new file mode 100644 index 00000000..75d303ea --- /dev/null +++ b/api/src/services/wage-enhancements/index.ts @@ -0,0 +1 @@ +export { ReplicateEstimatesService } from "./replicate-estimates-service" diff --git a/api/src/services/wage-enhancements/replicate-estimates-service.ts b/api/src/services/wage-enhancements/replicate-estimates-service.ts new file mode 100644 index 00000000..2c683bd5 --- /dev/null +++ b/api/src/services/wage-enhancements/replicate-estimates-service.ts @@ -0,0 +1,16 @@ +import BaseService from "@/services/base-service" + +export class ReplicateEstimatesService extends BaseService { + constructor( + private centreId: number, + private fiscalPeriodId: number + ) { + super() + } + + async perform() { + throw new Error("Not implemented") + } +} + +export default ReplicateEstimatesService diff --git a/web/src/api/wage-enhancements-api.ts b/web/src/api/wage-enhancements-api.ts index ebb650fe..c7c06d54 100644 --- a/web/src/api/wage-enhancements-api.ts +++ b/web/src/api/wage-enhancements-api.ts @@ -49,6 +49,12 @@ export const wageEnhancementsApi = { delete(wageEnhancementId: number): Promise { return http.delete(`/api/wage-enhancements/${wageEnhancementId}`).then(({ data }) => data) }, + + // Nested Endpoints + async replicateEstimates(params: { centreId: number; fiscalPeriodId: number }): Promise { + const { data } = await http.post("/api/wage-enhancements/replicate-estimates", params) + return data + }, } export default wageEnhancementsApi diff --git a/web/src/modules/centre/pages/CentreDashboardEmployeesMonthlyWorksheetPage.vue b/web/src/modules/centre/pages/CentreDashboardEmployeesMonthlyWorksheetPage.vue index 977e3a64..6d34fc81 100644 --- a/web/src/modules/centre/pages/CentreDashboardEmployeesMonthlyWorksheetPage.vue +++ b/web/src/modules/centre/pages/CentreDashboardEmployeesMonthlyWorksheetPage.vue @@ -18,7 +18,20 @@ -

Wage Enhancements

+

+ Wage Enhancements + + + + mdi-content-copy Replicate Estimates + +

import { computed, ref, watch } from "vue" +import { isEmpty, isNil } from "lodash" import { useNotificationStore } from "@/store/NotificationStore" import fiscalPeriodsApi, { FiscalPeriod } from "@/api/fiscal-periods-api" -import { isEmpty } from "lodash" +import wageEnhancementsApi from "@/api/wage-enhancements-api" import EditEmployeeBenefitWidget from "@/modules/centre/components/EditEmployeeBenefitWidget.vue" import EditWageEnhancementsWidget from "@/modules/centre/components/EditWageEnhancementsWidget.vue" @@ -103,6 +117,30 @@ watch( }, { immediate: true } ) + +const isReplicatingEstimates = ref(false) + +async function replicateEstimatesForward() { + const fiscalPeriodId = fiscalPeriod.value?.id + if (isNil(fiscalPeriodId)) { + throw new Error("Fiscal period ID is missing") + } + + isReplicatingEstimates.value = true + try { + await wageEnhancementsApi.replicateEstimates({ + centreId: props.centreId, + fiscalPeriodId: fiscalPeriodId, + }) + } catch (error) { + notificationStore.notify({ + text: `Failed to replicate wage enhancement estimates: ${error}`, + variant: "error", + }) + } finally { + isReplicatingEstimates.value = false + } +}