Skip to content

Commit

Permalink
Merge branch 'main' into js-332-accessibility-updates
Browse files Browse the repository at this point in the history
Conflicts:
	frontend/src/pages/ActivityReport/Pages/goalsObjectives.js
  • Loading branch information
jasalisbury committed Mar 11, 2021
2 parents c1527c2 + fad3c70 commit 30fb7c1
Show file tree
Hide file tree
Showing 25 changed files with 736 additions and 115 deletions.
27 changes: 24 additions & 3 deletions docs/logical_data_model.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
Logical Data Model
==================

![logical data model diagram](http://www.plantuml.com/plantuml/png/nLTjRzis4FwkNy5lUm5Y31k60KLGDCtS8S2M1N7w0yxIHUP6yRZZAQsHvBzFD1FPjEI4jR3rIm8zz_xmZaU_ym8iRMCANu91zSFibv-BuXi5TwGhvPYM1XklFcbPAR2rmKgLJ9-ykaMKml-jhGE6HXbjKTOE5R6ig2XA0zwhZnvPtHTAVQywRs-Kje9r1vsUrcYHGR1w-tOZNddqGViYg7bXRUi4jz3WhYJWPsqNYhLez9q8c65z6XM2ptqBjoABbsspHDw5iG5jTW7HOl2DGjis8GurU8sr9dknKy2tF_tudRgUVvXVpo_FvmlbsgNHvImg-fckDhaRyF8xrTfsXg8Rz0ADsqXAnb3xcGmahQj698_FXCO8UPIc1FGk5YKvFCQ3KXNwahKJJRP79vLpXTnexxDBQZronZc4XkbFWOyyFHzuoZ3Ry1ciffxiFss7pPc9VGuRvCA79hjxGYthtSy6PKN9H74CyvwgTGfxHzRZ8VoSAkpnYOMI6Mk_brhsXfmkispICD4kgxEOJCvX1w_zMKjmVr6zPhF9Odg3_4PWyZXGoWj8wtUHuF2MzzIq_y3KB1fWyIj4kH6uCX0QQaba4EoaZEEnlPHstSaoI0yklXBfb_XP5e9kEv5f1L2AljEolaP7BA-dTPReFwTdIJ0PhW3Sh1sEJzp16iRxXi4APexS5B9Yf8Dn1flts8kvZWNbD4xK38Q3cOW8owyWk5Sescg8JYOcT776NNHCWIarkNR1wjWnd31HP6FMnxn1feLEXoydFAEdbilD4pHPer1LEknUC9Sac7HLRFG_Rw-0HPVQ1QQJMQZGw3lazH-3vLor_KQliDGDcAGRGt5zsoOGz9Lr0JLQ2PV7oDf7JgaSpeHZgXcQO3_sg1RW6kMAN6iscOxfC88dEo9m2IFxudYpZme4OZq3IKbMyBCa1K2D2udi_EN9Tb03jWCvFQcZtvC6l7oCZVP-_Hy_Hf_CfhVyjXcp-dwusDGHc-tBhgDeOllh8SDkUvTC9YfD_voCWIKn2F7_oDSNDMGm6Ky_VzLCJVk83VAKTgX0FJoSqBsAhHwHHpevURZV-LrOn9-hGpnQZGd8FEl75sYn-QiU7pPGoA68NTEhzVkv27y6Q7GvcwdmVyVbE1I3RhsbI0mSAkz3F9MIOYdFmSdX3etKlQmozDS_RL-DbTHoI1JftHCdvxmdIZyJdUI-Yfm5-RXuUmaSrdc7jceRyoy0)
<img src="http://www.plantuml.com/plantuml/png/nLTVRzis47_Nf-3R_g0nYkrXG14KJJDt2B2vHfn-05gwoEme7dSyfJ6IVFUP8aA8YfpGrfQ-9F3l_j_zZgG-E9R4RQCBLqeDuktijxT5yOEGDwWgOGdN6XedlYdU26bg3PKe2RyvSGt0XVjR6Ij9Da8h0xor891uWqLHQkcS-EA0n5qXnCz2LUATK8QXta6dfpKO8CbGlN_VYJSEdDU-y6gEIPawmOrie2_n-5cx2qMA5RYTI1B9xMbGy3w75dQ31XPjimRgmz18fVi0AzRmJe1f6ny76xY4Mg6vw1Nmuw-__2Gkvv_cb_F5-NmbRBlMgfm5HJTJLPUxbex_n5gPdI6h1XpBndQIai3NViPF9AsjHYGFZmqD4V9QcZOWkbYLQE4O3q9Ku4fIbXMQY4ugPmdKAEUoIseTKCOSF67Y5EZ8wJdVfmophf_UQkfa_sbts9m8oULq0wt_eT3q9zIIwlziW3UOR1I5C1-nsgQmisWSxCFFiOA8JomKzHZzdgFi5LfLvjl4CC4kYrc4EcVOtckSDNlt5zLBDbqMqmdo1qgTrmCX74jkNH_n3gzfeARS9y9crKXYjm7WAvPBBJsXreWQf9cKSHZRSsdNErb6qxsBxzJq1TpoYi6wM49QWInPtIZUNe95ufVJEalq7zCpo90ibmUkjXR6Dsvl3UEy8MaOybPkKhD2fBrnXZVlqGTp7GhA8KxGz8OZE-4Hujybmw1GCDK8EhKndiLvPz711QQjoRNpLWUluVIDGXEoFl8DCY5qkdmvuGaSjbHsJz3a6fLLfLYXaNg130ch0j4_NoybiIgLzPrETg42hMw0jdyzkPohDjxe5PIgaJfXeoykqZQ1PAni5r21ONzuZ5pxG4ahHtR24jOa6sy_zgXcIJNm4cshkqp7T0YaGpCYi6b71k9mieyAMFfyHYGb6ePRJWgGDQmuTEUhGxE36cbs8AMJElezgQNZBu5e7xV_ysDqyhPzNdy5pusCPJlm90YfiM8DiQTlmh2MX3UgWYh11rHxyz-iSc_9yYeSNjZYY1lxi6Csc9Xux6NOBQBoP0AawP-ZrdAD91dfjVfrwA9vaptfUzwzLjh7xCEUjECIlchk-1WrmqFCS0BV_Ep4_Vq9tebmU0Na0KzExkzwN_f5zy4yWWUhCG7ggD-zwulkhtYuxo3V_IBiIrIBBwx2_tc21fUpcV3_Uox7eH0qUXiW8R2lF8PuB2N1qYbv9_jtCxJkMPKHV_5ZvSLOIItxHA6z0t4GXJ57rXJx-1xwdg-OYXDE3KJfpqGdkHN2VBnyNDnxZkxL-G2cQXlzBm00" alt="logical data model diagram">

UML Source
----------

```
@startuml
scale 0.75
scale 0.70
' avoid problems with angled crows feet
skinparam linetype ortho
Expand Down Expand Up @@ -170,6 +170,16 @@ class ActivityReport {
* updatedAt : timestamp
}
class Objective {
* id : integer <<generated>>
* goalId : integer(32) REFERENCES public.Goal.id
title : string,
ttaProvided : string,
status : string,
* createdAt : timestamp
* updatedAt : timestamp
}
class ActivityParticipant {
* id : integer <<generated>>
* activityReportId : integer(32) REFERENCES public.ActivityReport.id
Expand All @@ -191,6 +201,14 @@ class ActivityReportGoal {
* goalId : integer(32) REFERENCES public.Goal.id
}
class ActivityReportObjective {
* id : integer <<generated>>
* activityReportId : integer(32) REFERENCES public.ActivityReport.id
* objectiveId : integer(32) REFERENCES public.Objective.id
* createdAt : timestamp
* updatedAt : timestamp
}
User ||-o{ Region
User }o--|{ Permission
Scope }o--|{ Permission
Expand All @@ -211,6 +229,9 @@ ActivityReport .. NextSteps
ActivityReport .. ActivityReportGoal
Goal .. ActivityReportGoal
Goal }|--|{ ActivityReport
Goal ||-o{ Objective
ActivityReportObjective }o--|{ Objective
ActivityReportObjective }o--|{ ActivityReport
User ||-o{ ActivityReport
ActivityReport ||-o{ ActivityParticipant
Expand All @@ -222,7 +243,7 @@ NonGrantee ||-{ ActivityParticipant
Instructions
------------

1. [Edit this diagram with plantuml.com](http://www.plantuml.com/plantuml/uml/nLTjRzis4FwkNy5lUm5Y31k60KLGDCtS8S2M1N7w0yxIHUP6yRZZAQsHvBzFD1FPjEI4jR3rIm8zz_xmZaU_ym8iRMCANu91zSFibv-BuXi5TwGhvPYM1XklFcbPAR2rmKgLJ9-ykaMKml-jhGE6HXbjKTOE5R6ig2XA0zwhZnvPtHTAVQywRs-Kje9r1vsUrcYHGR1w-tOZNddqGViYg7bXRUi4jz3WhYJWPsqNYhLez9q8c65z6XM2ptqBjoABbsspHDw5iG5jTW7HOl2DGjis8GurU8sr9dknKy2tF_tudRgUVvXVpo_FvmlbsgNHvImg-fckDhaRyF8xrTfsXg8Rz0ADsqXAnb3xcGmahQj698_FXCO8UPIc1FGk5YKvFCQ3KXNwahKJJRP79vLpXTnexxDBQZronZc4XkbFWOyyFHzuoZ3Ry1ciffxiFss7pPc9VGuRvCA79hjxGYthtSy6PKN9H74CyvwgTGfxHzRZ8VoSAkpnYOMI6Mk_brhsXfmkispICD4kgxEOJCvX1w_zMKjmVr6zPhF9Odg3_4PWyZXGoWj8wtUHuF2MzzIq_y3KB1fWyIj4kH6uCX0QQaba4EoaZEEnlPHstSaoI0yklXBfb_XP5e9kEv5f1L2AljEolaP7BA-dTPReFwTdIJ0PhW3Sh1sEJzp16iRxXi4APexS5B9Yf8Dn1flts8kvZWNbD4xK38Q3cOW8owyWk5Sescg8JYOcT776NNHCWIarkNR1wjWnd31HP6FMnxn1feLEXoydFAEdbilD4pHPer1LEknUC9Sac7HLRFG_Rw-0HPVQ1QQJMQZGw3lazH-3vLor_KQliDGDcAGRGt5zsoOGz9Lr0JLQ2PV7oDf7JgaSpeHZgXcQO3_sg1RW6kMAN6iscOxfC88dEo9m2IFxudYpZme4OZq3IKbMyBCa1K2D2udi_EN9Tb03jWCvFQcZtvC6l7oCZVP-_Hy_Hf_CfhVyjXcp-dwusDGHc-tBhgDeOllh8SDkUvTC9YfD_voCWIKn2F7_oDSNDMGm6Ky_VzLCJVk83VAKTgX0FJoSqBsAhHwHHpevURZV-LrOn9-hGpnQZGd8FEl75sYn-QiU7pPGoA68NTEhzVkv27y6Q7GvcwdmVyVbE1I3RhsbI0mSAkz3F9MIOYdFmSdX3etKlQmozDS_RL-DbTHoI1JftHCdvxmdIZyJdUI-Yfm5-RXuUmaSrdc7jceRyoy0)
1. [Edit this diagram with plantuml.com](http://www.plantuml.com/plantuml/uml/nLTVRzis47_Nf-3R_g0nYkrXG14KJJDt2B2vHfn-05gwoEme7dSyfJ6IVFUP8aA8YfpGrfQ-9F3l_j_zZgG-E9R4RQCBLqeDuktijxT5yOEGDwWgOGdN6XedlYdU26bg3PKe2RyvSGt0XVjR6Ij9Da8h0xor891uWqLHQkcS-EA0n5qXnCz2LUATK8QXta6dfpKO8CbGlN_VYJSEdDU-y6gEIPawmOrie2_n-5cx2qMA5RYTI1B9xMbGy3w75dQ31XPjimRgmz18fVi0AzRmJe1f6ny76xY4Mg6vw1Nmuw-__2Gkvv_cb_F5-NmbRBlMgfm5HJTJLPUxbex_n5gPdI6h1XpBndQIai3NViPF9AsjHYGFZmqD4V9QcZOWkbYLQE4O3q9Ku4fIbXMQY4ugPmdKAEUoIseTKCOSF67Y5EZ8wJdVfmophf_UQkfa_sbts9m8oULq0wt_eT3q9zIIwlziW3UOR1I5C1-nsgQmisWSxCFFiOA8JomKzHZzdgFi5LfLvjl4CC4kYrc4EcVOtckSDNlt5zLBDbqMqmdo1qgTrmCX74jkNH_n3gzfeARS9y9crKXYjm7WAvPBBJsXreWQf9cKSHZRSsdNErb6qxsBxzJq1TpoYi6wM49QWInPtIZUNe95ufVJEalq7zCpo90ibmUkjXR6Dsvl3UEy8MaOybPkKhD2fBrnXZVlqGTp7GhA8KxGz8OZE-4Hujybmw1GCDK8EhKndiLvPz711QQjoRNpLWUluVIDGXEoFl8DCY5qkdmvuGaSjbHsJz3a6fLLfLYXaNg130ch0j4_NoybiIgLzPrETg42hMw0jdyzkPohDjxe5PIgaJfXeoykqZQ1PAni5r21ONzuZ5pxG4ahHtR24jOa6sy_zgXcIJNm4cshkqp7T0YaGpCYi6b71k9mieyAMFfyHYGb6ePRJWgGDQmuTEUhGxE36cbs8AMJElezgQNZBu5e7xV_ysDqyhPzNdy5pusCPJlm90YfiM8DiQTlmh2MX3UgWYh11rHxyz-iSc_9yYeSNjZYY1lxi6Csc9Xux6NOBQBoP0AawP-ZrdAD91dfjVfrwA9vaptfUzwzLjh7xCEUjECIlchk-1WrmqFCS0BV_Ep4_Vq9tebmU0Na0KzExkzwN_f5zy4yWWUhCG7ggD-zwulkhtYuxo3V_IBiIrIBBwx2_tc21fUpcV3_Uox7eH0qUXiW8R2lF8PuB2N1qYbv9_jtCxJkMPKHV_5ZvSLOIItxHA6z0t4GXJ57rXJx-1xwdg-OYXDE3KJfpqGdkHN2VBnyNDnxZkxL-G2cQXlzBm00)
2. Copy and paste the final UML into the UML Source section
3. Update the img src and edit link target to the current values

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Navigator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ function Navigator({
const [errorMessage, updateErrorMessage] = useState();
const [lastSaveTime, updateLastSaveTime] = useState(initialLastUpdated);
const [showValidationErrors, updateShowValidationErrors] = useState(false);
const { pageState } = formData;
const page = pages.find((p) => p.path === currentPage);

const hookForm = useForm({
mode: 'onChange',
defaultValues: formData,
shouldUnregister: false,
});
const pageState = hookForm.watch('pageState');

const {
formState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ const goalUrl = join('api', 'activity-reports', 'goals');

const RenderGoalsObjectives = ({
// eslint-disable-next-line react/prop-types
grantIds, activityRecipientType, initialData,
grantIds, activityRecipientType,
}) => {
// eslint-disable-next-line react/prop-types
const activityRecipients = grantIds.map((activityRecipientId) => ({ activityRecipientId }));
const data = { activityRecipientType, activityRecipients };
const hookForm = useForm({
mode: 'onChange',
defaultValues: { goals: [], ...initialData },
defaultValues: { goals: [], ...data },
});
// eslint-disable-next-line react/prop-types
const activityRecipients = grantIds.map((id) => ({ activityRecipientId: id }));
const data = { ...initialData, activityRecipientType, activityRecipients };
return (
<FormProvider {...hookForm}>
{goalsObjectives.render({}, data)}
{goalsObjectives.render()}
</FormProvider>
);
};
Expand Down Expand Up @@ -61,21 +61,21 @@ describe('goals objectives', () => {
afterEach(() => fetchMock.restore());
describe('when activity recipient type is "grantee"', () => {
it('the display goals section is displayed', async () => {
renderGoals([1], 'grantee', {});
renderGoals([1], 'grantee');
await screen.findByText('Context');
expect(await screen.findByText('Goals and objectives')).toBeVisible();
});

it('the display goals section does not show if no grants are selected', async () => {
renderGoals([], 'grantee', {});
renderGoals([], 'grantee');
await screen.findByText('Context');
expect(screen.queryByText('Goals and objectives')).toBeNull();
});
});

describe('when activity recipient type is not "grantee"', () => {
it('the display goals section is not displayed', async () => {
renderGoals([1], 'nonGrantee', {});
renderGoals([1], 'nonGrantee');
await screen.findByText('Context');
expect(screen.queryByText('Goals and objectives')).toBeNull();
});
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/pages/ActivityReport/Pages/activitySummary.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
targetPopulations,
} from '../constants';
import FormItem from '../../../components/FormItem';
import { NOT_STARTED } from '../../../components/Navigator/constants';

const ActivitySummary = ({
recipients,
Expand All @@ -34,6 +35,7 @@ const ActivitySummary = ({
const activityRecipientType = watch('activityRecipientType');
const startDate = watch('startDate');
const endDate = watch('endDate');
const pageState = watch('pageState');
const isVirtual = watch('deliveryMethod') === 'virtual';
const { nonGrantees: rawNonGrantees, grants: rawGrants } = recipients;

Expand Down Expand Up @@ -64,6 +66,12 @@ const ActivitySummary = ({
setValue('activityRecipients', []);
setValue('participants', []);
setValue('programTypes', []);
// Goals and objectives (page 3) has required fields when the recipient
// type is grantee, so we need to make sure that page is set as "not started"
// when recipient type is changed and we need to clear out any previously
// selected goals
setValue('goals', []);
setValue('pageState', { ...pageState, 3: NOT_STARTED });
previousActivityRecipientType.current = activityRecipientType;
}
}, [activityRecipientType, setValue]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const GoalPicker = ({

const onRemoveGoal = (id) => {
const newGoals = selectedGoals.filter((selectedGoal) => selectedGoal.id !== id);
updateNewAvailableGoals((goals) => goals.filter((goal) => goal !== id));
updateNewAvailableGoals(newGoals);
setValue('goals', newGoals);
};

Expand Down
42 changes: 14 additions & 28 deletions frontend/src/pages/ActivityReport/Pages/goalsObjectives.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Helmet } from 'react-helmet';
import {
Fieldset, Label, Textarea,
Expand All @@ -14,26 +13,29 @@ import GoalPicker from './components/GoalPicker';
import { getGoals } from '../../../fetchers/activityReports';
import { validateGoals } from './components/goalValidator';

const GoalsObjectives = ({
grantIds, activityRecipientType,
}) => {
const GoalsObjectives = () => {
const {
register,
register, watch,
} = useFormContext();
const recipients = watch('activityRecipients');
const activityRecipientType = watch('activityRecipientType');
const recipientGrantee = activityRecipientType === 'grantee';
const grantIds = recipientGrantee ? recipients.map((r) => r.activityRecipientId) : [];

const [availableGoals, updateAvailableGoals] = useState([]);
const hasGrants = grantIds.length > 0;

useDeepCompareEffect(() => {
const fetch = async () => {
if (activityRecipientType === 'grantee' && hasGrants) {
if (recipientGrantee && hasGrants) {
const fetchedGoals = await getGoals(grantIds);
updateAvailableGoals(fetchedGoals);
}
};
fetch();
}, [grantIds]);

const showGoals = activityRecipientType === 'grantee' && hasGrants;
const showGoals = recipientGrantee && hasGrants;

return (
<>
Expand Down Expand Up @@ -61,15 +63,7 @@ const GoalsObjectives = ({
);
};

GoalsObjectives.propTypes = {
grantIds: PropTypes.arrayOf(PropTypes.number),
activityRecipientType: PropTypes.string,
};

GoalsObjectives.defaultProps = {
activityRecipientType: '',
grantIds: [],
};
GoalsObjectives.propTypes = {};

const ReviewSection = () => {
const { watch } = useFormContext();
Expand Down Expand Up @@ -146,17 +140,9 @@ export default {
label: 'Goals and objectives',
path: 'goals-objectives',
review: false,
isPageComplete: (formData) => validateGoals(formData.goals) === true,
isPageComplete: (formData) => formData.activityRecipientType !== 'grantee' || validateGoals(formData.goals) === true,
reviewSection: () => <ReviewSection />,
render: (additionalData, formData) => {
const recipients = formData.activityRecipients || [];
const { activityRecipientType } = formData;
const grantIds = recipients.map((r) => r.activityRecipientId);
return (
<GoalsObjectives
activityRecipientType={activityRecipientType}
grantIds={grantIds}
/>
);
},
render: () => (
<GoalsObjectives />
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module.exports = {
up: async (queryInterface) => {
await queryInterface.dropTable('ActivityReportGoals');
},

down: async (queryInterface, Sequelize) => {
await queryInterface.createTable('ActivityReportGoals', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
activityReportId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'ActivityReports',
},
},
},
goalId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'Goals',
},
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
});
},
};
5 changes: 5 additions & 0 deletions src/migrations/20210310172128-add-grant-goal-unique-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
up: async (queryInterface) => queryInterface.addIndex('GrantGoals', ['grantId', 'goalId'], { unique: true }),

down: async (queryInterface) => queryInterface.removeIndex('GrantGoals', ['grantId', 'goalId']),
};
41 changes: 41 additions & 0 deletions src/migrations/20210310181116-create-objectives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
module.exports = {
up: async (queryInterface, Sequelize) => queryInterface.createTable('Objectives', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
goalId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'Goals',
},
key: 'id',
},
},
title: {
type: Sequelize.TEXT,
},
ttaProvided: {
type: Sequelize.TEXT,
},
status: {
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
}),

down: (queryInterface) => queryInterface.dropTable('Objectives'),
};
40 changes: 40 additions & 0 deletions src/migrations/20210310181122-create-activity-report-objectives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module.exports = {
up: async (queryInterface, Sequelize) => queryInterface.createTable('ActivityReportObjectives', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
activityReportId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'ActivityReports',
},
},
},
objectiveId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: {
tableName: 'Objectives',
},
},
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
defaultValue: Sequelize.fn('NOW'),
},
}),

down: (queryInterface) => queryInterface.dropTable('ActivityReportObjectives'),
};

0 comments on commit 30fb7c1

Please sign in to comment.