diff --git a/app/graphql/types/teams/membership_hour_value_chart_data_type.rb b/app/graphql/types/teams/member_hour_value_chart_data_type.rb similarity index 76% rename from app/graphql/types/teams/membership_hour_value_chart_data_type.rb rename to app/graphql/types/teams/member_hour_value_chart_data_type.rb index c0c5c6d91..5177c4eac 100644 --- a/app/graphql/types/teams/membership_hour_value_chart_data_type.rb +++ b/app/graphql/types/teams/member_hour_value_chart_data_type.rb @@ -2,7 +2,7 @@ module Types module Teams - class MembershipHourValueChartDataType < Types::BaseObject + class MemberHourValueChartDataType < Types::BaseObject field :date, GraphQL::Types::ISO8601Date field :hour_value_expected, Float field :hour_value_realized, Float diff --git a/app/graphql/types/teams/membership_hour_value_chart_list_type.rb b/app/graphql/types/teams/membership_hour_value_chart_list_type.rb index 3fe2798b4..2b43186be 100644 --- a/app/graphql/types/teams/membership_hour_value_chart_list_type.rb +++ b/app/graphql/types/teams/membership_hour_value_chart_list_type.rb @@ -3,8 +3,8 @@ module Types module Teams class MembershipHourValueChartListType < Types::BaseObject + field :member_hour_value_chart_data, [Types::Teams::MemberHourValueChartDataType] field :membership, Types::Teams::MembershipType - field :membership_hour_value_chart_data, [Types::Teams::MembershipHourValueChartDataType] end end end diff --git a/app/graphql/types/teams/team_member_hour_value_chart_list_type.rb b/app/graphql/types/teams/team_member_hour_value_chart_list_type.rb new file mode 100644 index 000000000..fa42af249 --- /dev/null +++ b/app/graphql/types/teams/team_member_hour_value_chart_list_type.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Types + module Teams + class TeamMemberHourValueChartListType < Types::BaseObject + field :member_hour_value_chart_data, [Types::Teams::MemberHourValueChartDataType] + field :team, Types::Teams::TeamType + end + end +end diff --git a/app/graphql/types/teams/team_member_type.rb b/app/graphql/types/teams/team_member_type.rb index ede401d33..3a3045553 100644 --- a/app/graphql/types/teams/team_member_type.rb +++ b/app/graphql/types/teams/team_member_type.rb @@ -14,55 +14,52 @@ class TeamMemberType < Types::BaseObject argument :page_number, Integer, required: false argument :until_date, GraphQL::Types::ISO8601Date, required: false end + field :demand_largest_lead_time, Types::DemandType, null: true + field :demand_lead_time_p80, Float, null: true + field :demand_shortest_lead_time, Types::DemandType, null: true + field :demands, [Types::DemandType] do + argument :limit, Int, required: false + argument :status, Types::Enums::DemandStatusesType, required: false + argument :type, String, required: false + end field :end_date, GraphQL::Types::ISO8601Date, null: true + field :first_delivery, Types::DemandType, null: true + field :first_demand_delivery, Types::DemandType, null: true field :hours_per_month, Int, null: false field :id, ID, null: false field :jira_account_id, String, null: true field :jira_account_user_email, String, null: true field :monthly_payment, Float, null: true field :name, String, null: false - field :start_date, GraphQL::Types::ISO8601Date, null: true - field :teams, [Types::Teams::TeamType], null: true - field :user, Types::UserType, null: true - - field :demands, [Types::DemandType] do - argument :limit, Int, required: false - argument :status, Types::Enums::DemandStatusesType, required: false - argument :type, String, required: false - end - field :projects_list, Types::ProjectsListType, null: true do argument :order_field, String, required: true argument :page_number, Int, required: false argument :per_page, Int, required: false argument :sort_direction, Types::Enums::SortDirection, required: false end + field :start_date, GraphQL::Types::ISO8601Date, null: true + field :teams, [Types::Teams::TeamType], null: true + field :user, Types::UserType, null: true - field :demand_largest_lead_time, Types::DemandType, null: true - field :demand_shortest_lead_time, Types::DemandType, null: true - field :first_demand_delivery, Types::DemandType, null: true - - field :demand_lead_time_p80, Float, null: true - field :first_delivery, Types::DemandType, null: true - + field :average_pull_interval_data, Types::Charts::SimpleDateChartDataType, null: true field :demand_blocks_list, Types::DemandBlocksListType, null: true do argument :order_field, String, required: true argument :page_number, Int, required: false argument :per_page, Int, required: false argument :sort_direction, Types::Enums::SortDirection, required: false end - - field :average_pull_interval_data, Types::Charts::SimpleDateChartDataType, null: true field :lead_time_control_chart_data, Types::Charts::ControlChartType, null: true field :lead_time_histogram_chart_data, Types::Charts::LeadTimeHistogramDataType, null: true field :member_effort_daily_data, Types::Charts::SimpleDateChartDataType, null: true field :member_effort_data, Types::Charts::SimpleDateChartDataType, null: true - field :member_throughput_data, [Int], null: true do argument :number_of_weeks, Int, required: false end - field :project_hours_data, Types::Charts::ProjectHoursChartDataType, null: true + field :team_member_hour_value_chart_list, [Types::Teams::TeamMemberHourValueChartListType], null: true do + argument :end_date, GraphQL::Types::ISO8601Date, required: false + argument :start_date, GraphQL::Types::ISO8601Date, required: false + end def demand_efforts(from_date: nil, until_date: nil, page_number: nil) efforts = object.demand_efforts.to_dates(from_date, until_date).order(start_time_to_computation: :desc) @@ -164,6 +161,24 @@ def project_hours_data { x_axis: team_chart_adapter.x_axis_hours_per_project, y_axis_projects_names: team_chart_adapter.y_axis_hours_per_project.pluck(:name), y_axis_hours: team_chart_adapter.y_axis_hours_per_project.pluck(:data).flatten } end + def team_member_hour_value_chart_list(start_date: 6.months.ago, end_date: Time.zone.now) + months = TimeService.instance.months_between_of(start_date, end_date) + + member_hour_value_list = [] + object.teams.each do |team| + member_hour_value_chart_data = [] + months.each do |month| + membership = object.memberships.active_for_date(month).find_by(team: team) + next if membership.blank? + + member_hour_value_chart_data.push({ date: month, hour_value_expected: membership.expected_hour_value(month), hour_value_realized: membership.realized_hour_value(month) }) + end + member_hour_value_list.push({ team: team, member_hour_value_chart_data: member_hour_value_chart_data }) + end + + member_hour_value_list + end + private def operations_dashboards @@ -197,12 +212,6 @@ def last_30_days_hash end accumulator end - - def compute_hours_per_month(monthly_payment, monthly_hours) - return monthly_payment if monthly_hours.zero? - - monthly_payment / monthly_hours - end end end end diff --git a/app/graphql/types/teams/team_type.rb b/app/graphql/types/teams/team_type.rb index c69c0d9ac..8e941dbcf 100644 --- a/app/graphql/types/teams/team_type.rb +++ b/app/graphql/types/teams/team_type.rb @@ -36,7 +36,7 @@ class TeamType < Types::BaseObject field :lead_time_p80, Float, null: true field :lead_time_p95, Float, null: true field :max_work_in_progress, Int, null: false - field :membership_hour_value_chart_list_type, [Types::Teams::MembershipHourValueChartListType] do + field :membership_hour_value_chart_list, [Types::Teams::MembershipHourValueChartListType] do argument :end_date, GraphQL::Types::ISO8601Date, required: false argument :start_date, GraphQL::Types::ISO8601Date, required: false end @@ -145,7 +145,7 @@ def team_consolidations_weekly(start_date: 6.months.ago, end_date: Time.zone.tod weekly_team_consolidations = object.team_consolidations.weekly_data.order(:consolidation_date) consolidations = Consolidations::TeamConsolidation - .where(id: weekly_team_consolidations.map(&:id) + [last_consolidation&.id]) + .where(id: weekly_team_consolidations.map(&:id) + [last_consolidation&.id]) consolidations = consolidations.where('consolidation_date >= :limit_date', limit_date: start_date) if start_date.present? consolidations = consolidations.where('consolidation_date <= :limit_date', limit_date: end_date) if end_date.present? consolidations.order(:consolidation_date) @@ -170,16 +170,16 @@ def team_member_efficiency(month: Time.zone.today.month, year: Time.zone.today.y TeamService.instance.compute_memberships_realized_hours(object, start_date, end_date) end - def membership_hour_value_chart_list_type(start_date: 6.months.ago, end_date: Time.zone.now) + def membership_hour_value_chart_list(start_date: 6.months.ago, end_date: Time.zone.now) months = TimeService.instance.months_between_of(start_date, end_date) memberships_hour_value_list = [] object.memberships.active.billable_member.each do |membership| - membership_hour_value_chart_data = [] + member_hour_value_chart_data = [] months.each do |month| - membership_hour_value_chart_data.push({ date: month, hour_value_expected: membership.expected_hour_value(month), hour_value_realized: membership.realized_hour_value(month) }) + member_hour_value_chart_data.push({ date: month, hour_value_expected: membership.expected_hour_value(month), hour_value_realized: membership.realized_hour_value(month) }) end - memberships_hour_value_list.push({ membership: membership, membership_hour_value_chart_data: membership_hour_value_chart_data }) + memberships_hour_value_list.push({ membership: membership, member_hour_value_chart_data: member_hour_value_chart_data }) end memberships_hour_value_list diff --git a/app/spa/src/components/TeamMemberDashboardCharts.tsx b/app/spa/src/components/TeamMemberDashboardCharts.tsx index 7629d7342..d7b36a082 100644 --- a/app/spa/src/components/TeamMemberDashboardCharts.tsx +++ b/app/spa/src/components/TeamMemberDashboardCharts.tsx @@ -82,21 +82,21 @@ const TeamMemberDashboardCharts = ({ } ) - const lineChartData = [ - { - id: teamMember.name, - data: teamMember.teamMemberConsolidationList - ? teamMember.teamMemberConsolidationList.map( - ({ valuePerHourPerformed, consolidationDate }) => { + const lineChartTeamMemberHourValueData = + teamMember?.teamMemberHourValueChartList?.map((teamMemberHourValueList) => { + return { + id: teamMemberHourValueList.team?.name ?? "", + data: + teamMemberHourValueList.memberHourValueChartData?.map( + (memberHourValueChartData) => { return { - x: String(consolidationDate || ""), - y: Number(valuePerHourPerformed || 0), + x: String(memberHourValueChartData.date || ""), + y: String(memberHourValueChartData.hourValueRealized || 0), } } - ) - : [], - }, - ] + ) ?? [], + } + }) ?? [] return ( @@ -162,7 +162,7 @@ const TeamMemberDashboardCharts = ({ {projectHoursData && ( { }, ] - const lineChartMembershipData = + const lineChartMembershipHourValueData = team?.membershipHourValueChartList?.map((membershipHourValueList) => { return { id: membershipHourValueList.membership?.teamMemberName ?? "", data: - membershipHourValueList.membershipHourValueChartData?.map( - (membershipHourValueChartData) => { + membershipHourValueList.memberHourValueChartData?.map( + (memberHourValueChartData) => { return { - x: String(membershipHourValueChartData.date || ""), - y: String(membershipHourValueChartData.hourValueRealized || 0), + x: String(memberHourValueChartData.date || ""), + y: String(memberHourValueChartData.hourValueRealized || 0), } } ) ?? [], @@ -344,7 +344,7 @@ const TeamDashboard = () => { [], 'teamMonthlyInvestment' => { 'xAxis' => ['2022-09-30'], 'yAxis' => [-4500.0] }, 'teamMemberEfficiency' => { 'membersEfficiency' => [{ 'effortInMonth' => 0.0, 'membership' => { 'teamMemberName' => 'aaa' }, 'realizedMoneyInMonth' => 0.0 }, { 'effortInMonth' => 0.0, 'membership' => { 'teamMemberName' => 'ddd' }, 'realizedMoneyInMonth' => 0.0 }] }, - 'membershipHourValueChartListType' => [{ 'date' => '2022-08-31', 'membershipHourValueChartData' => [{ 'hourValueExpected' => 15.625, 'hourValueRealized' => 25.0, 'membership' => { 'id' => membership.id.to_s } }, { 'hourValueExpected' => 12.5, 'hourValueRealized' => 0.0, 'membership' => { 'id' => other_membership.id.to_s } }] }, { 'date' => '2022-09-30', 'membershipHourValueChartData' => [{ 'hourValueExpected' => 15.625, 'hourValueRealized' => 0.0, 'membership' => { 'id' => membership.id.to_s } }, { 'hourValueExpected' => 12.5, 'hourValueRealized' => 0.0, 'membership' => { 'id' => other_membership.id.to_s } }] }], + 'membershipHourValueChartList' => [{ 'memberHourValueChartData' => [{ 'date' => '2022-08-31', 'hourValueExpected' => 15.625, 'hourValueRealized' => 25.0 }, { 'date' => '2022-09-30', 'hourValueExpected' => 15.625, 'hourValueRealized' => 0.0 }], 'membership' => { 'id' => membership.id.to_s } }, { 'memberHourValueChartData' => [{ 'date' => '2022-08-31', 'hourValueExpected' => 12.5, 'hourValueRealized' => 0.0 }, { 'date' => '2022-09-30', 'hourValueExpected' => 12.5, 'hourValueRealized' => 0.0 }], 'membership' => { 'id' => other_membership.id.to_s } }], 'memberships' => [{ 'id' => other_membership.id.to_s, 'memberRoleDescription' => 'Cliente' }, { 'id' => membership.id.to_s, 'memberRoleDescription' => 'Desenvolvedor' }], 'lastReplenishingConsolidations' => [ @@ -1761,10 +1761,11 @@ other_project = Fabricate :project, start_date: 2.weeks.ago, end_date: 2.days.from_now team = Fabricate :team, company: company - team_member = Fabricate :team_member, company: company, monthly_payment: 1000 + other_team = Fabricate :team, company: company + team_member = Fabricate :team_member, company: company, monthly_payment: 1000, hours_per_month: 160 another_team_member = Fabricate :team_member, company: company, monthly_payment: 1000 - membership = Fabricate :membership, team_member: team_member, team: team - another_membership = Fabricate :membership, team_member: another_team_member, team: team + membership = Fabricate :membership, team_member: team_member, team: team, end_date: nil, hours_per_month: 100 + another_membership = Fabricate :membership, team_member: team_member, team: other_team, end_date: nil, hours_per_month: 60 demand_finished = Fabricate :demand, team: team, project: project, created_date: 2.days.ago, commitment_date: 10.hours.ago, end_date: 1.hour.ago, work_item_type: feature_type other_demand_finished = Fabricate :demand, team: team, project: other_project, created_date: 3.days.ago, commitment_date: 6.hours.ago, end_date: 2.hours.ago, work_item_type: bug_type bug = Fabricate :demand, team: team, project: project, created_date: 2.days.ago, end_date: nil, work_item_type: bug_type @@ -1912,6 +1913,17 @@ yAxisHours } memberThroughputData(numberOfWeeks: 3) + + teamMemberHourValueChartList(startDate: "#{1.month.ago.to_date.iso8601}") { + team { + id + name + } + memberHourValueChartData { + date + hourValueRealized + } + } } }) @@ -1948,17 +1960,12 @@ 'billable' => team_member.billable, 'hoursPerMonth' => team_member.hours_per_month, 'monthlyPayment' => team_member.monthly_payment.to_f, - 'teams' => [{ - 'name' => team.name - }], + 'teams' => [{ 'name' => team.name }, { 'name' => other_team.name }], 'projectsEndDateAsc' => { 'lastPage' => false, 'totalCount' => 2, 'totalPages' => 2, - 'projects' => - [{ - 'id' => project.id.to_s - }] + 'projects' => [{ 'id' => project.id.to_s }] }, 'projectsEndDateDesc' => { 'lastPage' => false, @@ -1968,68 +1975,25 @@ 'id' => other_project.id.to_s }] }, - 'demandsFinished' => [ - { - 'id' => demand_finished.id.to_s - }, - { - 'id' => other_demand_finished.id.to_s - } - ], - 'bugs' => [ - { - 'id' => other_demand_finished.id.to_s - }, - { - 'id' => bug.id.to_s - }, - { - 'id' => other_bug.id.to_s - } - ], - 'bugsFinished' => [ - { - 'id' => other_demand_finished.id.to_s - } - ], - 'lastDeliveries' => [ - { - 'id' => demand_finished.id.to_s - } - ], - 'demandShortestLeadTime' => - { - 'id' => other_demand_finished.id.to_s - }, - 'demandLargestLeadTime' => - { - 'id' => demand_finished.id.to_s - }, + 'demandsFinished' => [{ 'id' => demand_finished.id.to_s }, { 'id' => other_demand_finished.id.to_s }], + 'bugs' => [{ 'id' => other_demand_finished.id.to_s }, { 'id' => bug.id.to_s }, { 'id' => other_bug.id.to_s }], + 'bugsFinished' => [{ 'id' => other_demand_finished.id.to_s }], + 'lastDeliveries' => [{ 'id' => demand_finished.id.to_s }], + 'demandShortestLeadTime' => { 'id' => other_demand_finished.id.to_s }, + 'demandLargestLeadTime' => { 'id' => demand_finished.id.to_s }, 'demandLeadTimeP80' => Stats::StatisticsService.instance.percentile(80, team_member.demands.finished_with_leadtime.map(&:leadtime)), - 'firstDelivery' => - { - 'id' => other_demand_finished.id.to_s - }, + 'firstDelivery' => { 'id' => other_demand_finished.id.to_s }, 'demandBlocksListDesc' => { 'totalPages' => 2, 'lastPage' => false, 'totalCount' => 2, - 'demandBlocks' => [ - { - 'id' => demand_block.id.to_s - } - ] - + 'demandBlocks' => [{ 'id' => demand_block.id.to_s }] }, 'demandBlocksListAsc' => { 'totalPages' => 2, 'lastPage' => false, 'totalCount' => 2, - 'demandBlocks' => [ - { - 'id' => other_demand_block.id.to_s - } - ] + 'demandBlocks' => [{ 'id' => other_demand_block.id.to_s }] }, 'leadTimeControlChartData' => { 'xAxis' => [other_demand_finished.external_id, demand_finished.external_id], @@ -2038,17 +2002,14 @@ 'leadTimeP80' => lead_time_p80, 'leadTimeP95' => lead_time_p95 }, - 'leadTimeHistogramChartData' => { - 'keys' => [23_400.0], - 'values' => [2] - }, + 'leadTimeHistogramChartData' => { 'keys' => [23_400.0], 'values' => [2] }, 'memberEffortData' => { 'xAxis' => %w[2021-11-01 2021-12-01 2022-01-01 2022-02-01 2022-03-01 2022-04-01 2022-05-01], - 'yAxis' => [0.0, 0.0, 0.0, 0.0, 100.0, 100.0, 340.0] + 'yAxis' => [0.0, 0.0, 0.0, 0.0, 100.0, 100.0, 10_340.0] }, 'memberEffortDailyData' => { 'xAxis' => %w[2022-04-18 2022-04-19 2022-04-20 2022-04-21 2022-04-22 2022-04-23 2022-04-24 2022-04-25 2022-04-26 2022-04-27 2022-04-28 2022-04-29 2022-04-30 2022-05-01 2022-05-02 2022-05-03 2022-05-04 2022-05-05 2022-05-06 2022-05-07 2022-05-08 2022-05-09 2022-05-10 2022-05-11 2022-05-12 2022-05-13 2022-05-14 2022-05-15 2022-05-16 2022-05-17 2022-05-18 2022-05-20], - 'yAxis' => [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 150.0, 20.0, 0.0, 170.0] + 'yAxis' => [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 10_150.0, 20.0, 0.0, 170.0] }, 'averagePullIntervalData' => { 'xAxis' => %w[2022-03-18 2022-04-18 2022-05-18], @@ -2056,16 +2017,13 @@ }, 'projectHoursData' => { 'xAxis' => ['2022-05-31'], - 'yAxisHours' => [170.0], + 'yAxisHours' => [10_170.0], 'yAxisProjectsNames' => [project.name] }, 'memberThroughputData' => [0, 0, 0, 2], - 'demandEfforts' => [{ - 'finishTimeToComputation' => '2022-05-03T10:00:00-03:00' - }], - 'demandEffortsList' => { - 'demandEffortsCount' => 1 - } + 'demandEfforts' => [{ 'finishTimeToComputation' => '2022-05-03T10:00:00-03:00' }], + 'demandEffortsList' => { 'demandEffortsCount' => 1 }, + 'teamMemberHourValueChartList' => [{ 'memberHourValueChartData' => [{ 'date' => '2022-05-31', 'hourValueRealized' => 1.838235294117647 }], 'team' => { 'id' => team.id.to_s, 'name' => team.name } }, { 'memberHourValueChartData' => [{ 'date' => '2022-05-31', 'hourValueRealized' => 0.0375 }], 'team' => { 'id' => other_team.id.to_s, 'name' => other_team.name } }] }) end end