Skip to content
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

Release FY24Q3.4.0 #21852

Merged
merged 16 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ detectors:
- UpdateCachedAppealsAttributesJob
- Veteran
- LegacyDocket
- Test::UsersController
TooManyConstants:
exclude:
- Fakes::BGSServicePOA
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/metrics/v2/logs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def create
private

def metrics_not_saved
render json: { error_code: "Metrics not saved for user" }, status: :unprocessable_entity
render json: { error_code: "Metrics not saved for user" }, status: :accepted
end

def allowed_params
Expand Down
9 changes: 8 additions & 1 deletion app/controllers/test/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
CaseflowCertification::Application.load_tasks

class Test::UsersController < ApplicationController
before_action :require_demo, only: [:set_user, :set_end_products, :reseed, :toggle_feature]
before_action :require_demo, only: [:set_user, :set_end_products, :reseed, :optional_seed, :toggle_feature]
before_action :require_global_admin, only: :log_in_as_user
skip_before_action :deny_vso_access, only: [:index, :set_user, :show]

Expand Down Expand Up @@ -110,6 +110,13 @@ def reseed
head :ok
end

def optional_seed
return unless Rails.deploy_env?(:demo)

system "bundle exec rake db:seed:optional"
head :ok
end

def toggle_feature
params[:enable]&.each do |f|
FeatureToggle.enable!(f[:value])
Expand Down
5 changes: 4 additions & 1 deletion app/models/appeal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,6 @@ def clone_task_tree(parent_appeal, user_css_id)
parent_ordered_tasks = parent_appeal.tasks.order(:created_at)
# define hash to store parent/child relationship values
task_parent_to_child_hash = {}

while parent_appeal.tasks.count != tasks.count && !parent_appeal.tasks.nil?
# cycle each task in the parent
parent_ordered_tasks.each do |task|
Expand Down Expand Up @@ -698,6 +697,10 @@ def cavc
court_remand?
end

def predocketed?
tasks.select { |task| task.class.name == "PreDocketTask" && task.open? }
end

def vha_predocket_needed?
request_issues.active.any?(&:vha_predocket?)
end
Expand Down
2 changes: 1 addition & 1 deletion app/models/bgs_power_of_attorney.rb
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def save_with_updated_bgs_record!

def update_ihp_task
related_appeals.each do |appeal|
InformalHearingPresentationTask.update_to_new_poa(appeal) if appeal.active?
InformalHearingPresentationTask.update_to_new_poa(appeal) if appeal.active? && !appeal.predocketed?
end
end

Expand Down
2 changes: 0 additions & 2 deletions app/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -957,8 +957,6 @@ def status_is_valid_on_create
if status != Constants.TASK_STATUSES.assigned
fail Caseflow::Error::InvalidStatusOnTaskCreate, task_type: type
end

true
end

def assignee_status_is_valid_on_create
Expand Down
10 changes: 9 additions & 1 deletion app/models/tasks/schedule_hearing_colocated_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,18 @@ def just_completed_ama_organization_task?
assigned_to.is_a?(Organization)
end

# Selects all judge tasks that are NOT QualityReviewJudgeTasks
def handle_judge_tasks!
judge_tasks = JudgeTask.open.where(appeal: appeal)
non_quality_review_judge_tasks = judge_tasks.to_a.reject { |task| task.type == "JudgeQualityReviewTask" }
# Converts array to active record association and runs cancel_task_and_child_subtasks
JudgeTask.where(id: non_quality_review_judge_tasks.map(&:id)).find_each(&:cancel_task_and_child_subtasks)
end

def send_to_hearings_branch
parent = DistributionTask.create!(appeal: appeal, parent: appeal.root_task)
ScheduleHearingTask.create!(appeal: appeal, parent: parent)
JudgeTask.open.where(appeal: appeal).find_each(&:cancel_task_and_child_subtasks)
handle_judge_tasks!
DistributedCase.find_by(case_id: appeal.uuid)&.rename_for_redistribution!
end
end
4 changes: 4 additions & 0 deletions app/models/tasks/schedule_hearing_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ def default_instructions
end

def create_parent_hearing_task
return true unless parent

if parent.type != HearingTask.name
self.parent = HearingTask.create(appeal: appeal, parent: parent)
end

true
end

def verify_vso_can_change_hearing_to_virtual!(params)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ def cancel_tasks_blocking_distribution

def verify_appeal_distributable
if DistributionTask.open.where(appeal: appeal).empty?
return true if appeal.appeal_split_process == true

fail(Caseflow::Error::IneligibleForBlockedSpecialCaseMovement, appeal_id: appeal.id)
end
end
Expand Down
27 changes: 15 additions & 12 deletions app/models/tasks/special_case_movement_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,25 @@ def self.label
private

def distribute_to_judge
Task.transaction do
JudgeAssignTask.create!(appeal: appeal,
parent: appeal.root_task,
assigned_to: assigned_to,
assigned_by: assigned_by,
instructions: instructions)
# We don't want the judge to have to worry about the SpecialCaseMovementTask,
# so we assign it to the SCM user that assigned this.
update!(status: Constants.TASK_STATUSES.completed, assigned_to: assigned_by)
# For now, we expect the parent to always be the distribution task
# so we don't worry about distribution task explicitly
parent.update!(status: Constants.TASK_STATUSES.completed)
unless appeal.appeal_split_process
Task.transaction do
JudgeAssignTask.create!(appeal: appeal,
parent: appeal.root_task,
assigned_to: assigned_to,
assigned_by: assigned_by,
instructions: instructions)
# We don't want the judge to have to worry about the SpecialCaseMovementTask,
# so we assign it to the SCM user that assigned this.
update!(status: Constants.TASK_STATUSES.completed, assigned_to: assigned_by)
# For now, we expect the parent to always be the distribution task
# so we don't worry about distribution task explicitly
parent.update!(status: Constants.TASK_STATUSES.completed)
end
end
end

def verify_appeal_distributable
return true if appeal.appeal_split_process
if !appeal.ready_for_distribution?
fail(Caseflow::Error::IneligibleForSpecialCaseMovement, appeal_id: appeal.id)
end
Expand Down
2 changes: 1 addition & 1 deletion app/services/external_api/va_dot_gov_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ def send_facilities_requests(ids:, query:)
response = nil

until remaining_ids.empty? || response.try(:next?) == false || response.try(:success?) == false
response = send_facilities_request(query: query.merge(page: page, perPage: 200)).merge(response)
response = send_facilities_request(query: query.merge(page: page, per_page: 200)).merge(response)

remaining_ids -= response.data.pluck(:facility_id)

Expand Down
3 changes: 1 addition & 2 deletions app/services/metrics_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ def self.record(description, service: nil, name: "unknown", caller: nil)
app_name: app,
attrs: {
service: service,
endpoint: name,
uuid: uuid
endpoint: name
}
}
MetricsService.emit_gauge(sent_to_info)
Expand Down
18 changes: 18 additions & 0 deletions app/workflows/ihp_tasks_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ def initialize(parent)

def create_ihp_tasks!
appeal = @parent.appeal

if appeal.status.send(:open_pre_docket_task?)
cancel_any_existing_ihp_tasks(appeal)
return []
end

appeal.representatives.select { |org| org.should_write_ihp?(appeal) }.map do |vso_organization|
# For some RAMP appeals, this method may run twice.
existing_task = InformalHearingPresentationTask.find_by(
Expand All @@ -22,4 +28,16 @@ def create_ihp_tasks!
)
end
end

private

def cancel_any_existing_ihp_tasks(appeal)
appeal.representatives.select { |org| org.should_write_ihp?(appeal) }.each do |vso_organization|
existing_task = InformalHearingPresentationTask.find_by(
appeal: appeal,
assigned_to: vso_organization
)
existing_task&.update!(status: Constants.TASK_STATUSES.cancelled)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def create_send_final_notification_letter_tasks
appeal: @task.appeal,
parent: @task.parent,
assigned_to: Organization.find_by_url("clerk-of-the-board"),
assigned_by: current_user
assigned_by: @task.assigned_by
)
# sfnlt.instructions.push(instructions)
sfnlt.update!(status: Constants.TASK_STATUSES.assigned)
Expand Down
26 changes: 24 additions & 2 deletions client/app/components/LoadingDataDisplay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,38 @@ import LoadingScreen from './LoadingScreen';
import StatusMessage from './StatusMessage';
import COPY from '../../COPY';
import { recordAsyncMetrics } from '../util/Metrics';
import { ExternalLinkIcon } from './icons';
import { css } from 'glamor';
import Link from './Link';

const ICON_POSITION_FIX = css({ position: 'relative', top: 3 });

const PROMISE_RESULTS = {
SUCCESS: 'SUCCESS',
FAILURE: 'FAILURE'
};

const ESCALATION_FORM_URL = 'https://leaf.va.gov/VBA/335/sensitive_level_access_request/';

const accessDeniedTitle = { title: COPY.ACCESS_DENIED_TITLE };
const accessDeniedMsg = <div>
It looks like you do not have the necessary level of access to view this information.<br />
Please check with your application administrator before trying again.</div>;
VBA employs a sensitive access system and to access records at any designated level requires approval for the same or
higher-level access.<br />
You are receiving this message because you do not have an authorized access level required to view this page.<br />
<br />
To request access, please click the button below
<div>
<Link href={ESCALATION_FORM_URL}>
<button className="btn btn-default">Request Access &nbsp;
<span {...ICON_POSITION_FIX}><ExternalLinkIcon /></span>
</button>
</Link>
</div>
<br />
If you have any questions or need assistance with the request form linked above,
please contact the Restricted Portfolio Management team at
<a href="mailto:VBA.RPM@va.gov">VBA.RPM@va.gov</a>.
</div>;

const duplicateNumberTitle = { title: COPY.DUPLICATE_PHONE_NUMBER_TITLE };
const duplicateNumberMsg = <div>
Expand Down
32 changes: 14 additions & 18 deletions client/app/reader/PdfFile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export class PdfFile extends React.PureComponent {
file: this.props.file,
documentType: this.props.documentType,
prefetchDisabled: this.props.featureToggles.prefetchDisabled,
overscan: this.props.windowingOverscan
overscan: this.props.windowingOverscan,
isPageVisible: this.props.isVisible,
name: null
};
}

Expand Down Expand Up @@ -467,32 +469,26 @@ export class PdfFile extends React.PureComponent {
clearTimeout(this.scrollTimer);
}

this.scrollTimer = setTimeout(() => {
const scrollStart = performance.now();
const scrollStart = performance.now();

const data = {
overscan: this.props.windowingOverscan,
documentType: this.props.documentType,
pageCount: this.props.pdfDocument.numPages,
pageIndex: this.pageIndex,
prefetchDisabled: this.props.featureToggles.prefetchDisabled,
start: scrollStart,
end: performance.now()
};
this.scrollTimer = setTimeout(() => {
const scrollEnd = performance.now();
const scrollMessage = `Scroll to page ${this.currentPage + 1}
(${(Math.round(this.scrollLeft * 100) / 100).toFixed(2)},
${(Math.round(this.scrollTop * 100) / 100).toFixed(2)})`;

const posx = (Math.round(this.scrollLeft * 100) / 100).toFixed(2);
const posy = (Math.round(this.scrollTop * 100) / 100).toFixed(2);
this.metricsAttributes.name = scrollMessage;

storeMetrics(
this.props.documentId,
this.metricsAttributes,
{
message: `Scroll to position ${posx}, ${posy}`,
message: scrollMessage,
type: 'performance',
product: 'reader',
start: new Date(performance.timeOrigin + data.start),
end: new Date(performance.timeOrigin + data.end),
duration: data.start ? data.end - data.start : 0
start: new Date(performance.timeOrigin + scrollStart),
end: new Date(performance.timeOrigin + scrollEnd),
duration: scrollStart ? scrollEnd - scrollStart : 0
},
this.metricsIdentifier,
);
Expand Down
12 changes: 12 additions & 0 deletions client/app/reader/PdfPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ export class PdfPage extends React.PureComponent {
this.isDrawing = false;
this.renderTask = null;
this.marks = [];

this.metricsAttributes = {
documentId: this.props.documentId,
numPagesInDoc: null,
pageIndex: this.props.pageIndex,
file: this.props.file,
documentType: this.props.documentType,
prefetchDisabled: this.props.featureToggles.prefetchDisabled,
overscan: this.props.windowingOverscan,
isPageVisible: this.props.isVisible,
name: null
};
}

getPageContainerRef = (pageContainer) => (this.pageContainer = pageContainer);
Expand Down
3 changes: 1 addition & 2 deletions client/app/reader/ReaderLoadingScreen.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ export class ReaderLoadingScreen extends React.Component {
this.props.onReceiveAnnotations(annotations);
}).
catch((err) => {
// allow HTTP errors to fall on the floor via the console.
console.error(new Error(`Problem with GET /reader/appeal/${this.props.vacolsId}/documents?json ${err}`));
throw err;
});
}

Expand Down
24 changes: 22 additions & 2 deletions client/app/test/TestUsers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export default function TestUsers(props) {
const [isLoggingIn, setIsLoggingIn] = useState(false);
const [reseedingError, setReseedingError] = useState(null);
const [isReseeding, setIsReseeding] = useState(false);
const [optionalSeedingError, setOptionalSeedingError] = useState(null);
const [isOptionalSeeding, setIsOptionalSeeding] = useState(false);
const [inputValue, setInputValue] = useState('');

const handleEpSeed = (type) => ApiUtil.post(`/test/set_end_products?type=${type}`).
Expand Down Expand Up @@ -80,6 +82,18 @@ export default function TestUsers(props) {
});
};

const optionalSeed = () => {
setIsOptionalSeeding(true);
ApiUtil.post('/test/optional_seed').then(() => {
setOptionalSeedingError(null);
setIsOptionalSeeding(false);
}, (err) => {
console.warn(err);
setOptionalSeedingError(err);
setIsOptionalSeeding(false);
});
};

const filteredUserOptions = useMemo(() => {
const userOptions = props.testUsersList.map((user) => ({
value: user.id,
Expand Down Expand Up @@ -215,9 +229,9 @@ export default function TestUsers(props) {
Not all applications are available to every user. Additionally,
some users have access to different parts of the same application.
<br />This button reseeds the database with default values.</p>
{reseedingError &&
{(reseedingError || optionalSeedingError) &&
<Alert
message={reseedingError.toString()}
message={reseedingError ? reseedingError.toString() : optionalSeedingError.toString()}
type="error"
/>
}
Expand All @@ -226,6 +240,12 @@ export default function TestUsers(props) {
name="Reseed the DB"
loading={isReseeding}
loadingText="Reseeding the DB" />
<br />
<Button
onClick={optionalSeed}
name="Run optional seeds"
loading={isOptionalSeeding}
loadingText="Running optional seed" />
<br /> <br />
<h3>Global Feature Toggles Enabled:</h3>
<SearchableDropdown
Expand Down
Loading
Loading