Skip to content

Commit

Permalink
Merge pull request #432 from 51ngul4r1ty/story/000420/switch-projects
Browse files Browse the repository at this point in the history
Story/000420/switch projects
  • Loading branch information
51ngul4r1ty authored Sep 16, 2022
2 parents 0ed5a53 + 9296b4c commit ab476a7
Show file tree
Hide file tree
Showing 22 changed files with 405 additions and 142 deletions.
1 change: 1 addition & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"andrewleedham.vscode-css-modules",
"esbenp.prettier-vscode",
"gruntfuggly.todo-tree",
"jock.svg",
"msjsdiag.debugger-for-chrome",
"orta.vscode-jest",
"ryanluker.vscode-coverage-gutters",
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ section.

Everyone contributing to this repo should read this document before doing anything: [IMPORTANT.md](docs/IMPORTANT.md)

For specialized instructions (to save you time trying to do various things): [HOWTO.md](docs/HOWTO.md)
For specialized instructions (to save you time trying to do various things): [DEV_HOWTO.md](docs/DEV_HOWTO.md)

Many code standards and conventions can be picked up from existing patterns in the code but it is advisable to use this resource as
well: [CODE_STANDARDS.md](docs/CODE_STANDARDS.md)
Expand Down
3 changes: 2 additions & 1 deletion atoll-core-main.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"deno.import_intellisense_origins": {
"https://deno.land": true
},
"jest.jestCommandLine": "node_modules/.bin/jest"
"jest.jestCommandLine": "node_modules/.bin/jest",
"svg.preview.background": "dark-transparent"
}
}
2 changes: 1 addition & 1 deletion docs/CODE_STANDARDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Middleware should have `next(action);` as the first line to ensure that state is

Ensure that you're using the correct types like this:
```
export const apiOrchestrationMiddleware = (store: StoreTyped) => (next) => (action: Action) => {
export const apiOrchestrationMiddleware: Middleware<{}, StateTree> = (store: StoreTyped) => (next) => (action: Action) => {
```

State retrieval is very common in middleware so it should be done at the start of the middleware like below.
Expand Down
39 changes: 39 additions & 0 deletions docs/DEV_HOWTO.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,42 @@ export default connect(
mapDispatchToProps
)(withTranslation()<any>(App));
```

Generating React Components from SVG Files
==========================================

This app relies on fast rendering of images so they're all in-lined. This should also make it easier for code-splitting,
lazy-loading, etc.

Steps to Generate SVG Files (if using Affinity Designer)
--------------------------------------------------------

1. The `atoll-shared` repo contains all the SVG assets and generated components.
Use terminal and `cd atoll-shared` to execute all commands below in this folder.
2. The original SVG file should be stored in the `/assets` folder.
3. Make sure to name components using the convention: `{lowercase-component-name}-icon.svg`,
for example `menu-caret-down-icon.svg`
4. Use `npm run gen:react-svg -- menu-caret-down-icon` to generate the basic React component `MenuCareDownIcon.tsx`.
5. The file will be placed in the `/components/atoms/icons` folder.
6. Affinity places additional SVG elements that aren't needed so you should remove them:
- remove the line with `xmlnsSerif=`
- remove the attribute `serifId="{component-title}"` (for example, `serifId="Menu Caret"`)
- change `const classNameToUse = buildClassName(props.className);`
to `const classNameToUse = buildClassName(strokeClass, props.className);`
- add `fill="none"` and `className={classNameToUse}` attributes to the top of the `svg` element.
- remove `id` attribute from the `g` element and replace it with `className={fillClass}`,
for example, `<g id="Menu-Caret" transform=...`
becomes `<g className={fillClass} transform...`
7. Make sure you sort the `index.ts` file (it should have the new component added to it).
8. Make sure you follow the steps to add this icon component to storybook.

Adding Icon Component to Storybook
----------------------------------

1. In all cases before, make sure to add new entries in alphabetical order.
2. Find the `allicons.stories.tsx` file (it should be in the `/stories/atoms/icons` folder).
3. Add component by name to the import list under `// components` section.
4. Add component to `invertableIcons`, `icons` object definitions.
5. Add component to `iconNames` list.
6. Make sure to build the package (`npm run build`) and then use `npm run storybook` to view
the component to ensure that it displays correctly.
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "atoll",
"version": "0.61.0",
"version": "0.62.0",
"author": {
"name": "Kevin Berry",
"email": "[email protected]"
Expand Down Expand Up @@ -59,7 +59,7 @@
"check:prereqs": "ts-node ./scripts/check-prereqs.ts"
},
"dependencies": {
"@atoll/shared": "0.61.0",
"@atoll/shared": "0.62.0",
"@flopflip/memory-adapter": "1.6.0",
"@flopflip/react-broadcast": "10.1.11",
"axios": "0.26.1",
Expand Down
24 changes: 13 additions & 11 deletions src/common/routeBuilder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,25 @@ import {
PlanViewContainer,
ReviewViewContainer,
SprintViewContainer,
layouts
layouts,
HOME_VIEW_ROUTE,
PLAN_VIEW_ROUTE,
SPRINT_VIEW_ROUTE,
REVIEW_VIEW_ROUTE,
DEBUG_PBI_VIEW_ROUTE,
BACKLOGITEM_VIEW_ROUTE
} from "@atoll/shared";

const appRoutes = (
<layouts.MainLayout>
<AppContainer>
<Switch>
<Route path="/" exact component={LoginViewContainer} />
<Route path="/plan" exact component={PlanViewContainer} />
<Route path="/sprint" exact component={SprintViewContainer} />
<Route path="/review" exact component={ReviewViewContainer} />
<Route path="/debug/product-backlog-items" exact component={ProductBacklogItemViewContainer} />
<Route
path="/project/:projectDisplayId/backlog-item/:backlogItemDisplayId"
exact
component={BacklogItemViewContainer}
/>
<Route path={HOME_VIEW_ROUTE} exact component={LoginViewContainer} />
<Route path={PLAN_VIEW_ROUTE} exact component={PlanViewContainer} />
<Route path={SPRINT_VIEW_ROUTE} exact component={SprintViewContainer} />
<Route path={REVIEW_VIEW_ROUTE} exact component={ReviewViewContainer} />
<Route path={DEBUG_PBI_VIEW_ROUTE} exact component={ProductBacklogItemViewContainer} />
<Route path={BACKLOGITEM_VIEW_ROUTE} exact component={BacklogItemViewContainer} />
</Switch>
</AppContainer>
</layouts.MainLayout>
Expand Down
59 changes: 33 additions & 26 deletions src/server/api/handlers/backlogItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,51 +194,58 @@ export const backlogItemsDeleteHandler = async (req: Request, res: Response) =>
}
};

const getNewCounterValue = async (projectId: string, backlogItemType: string) => {
/**
* Get a new counter value - caller is responsible for managing the transaction.
*/
const getNewCounterValue = async (projectId: string, backlogItemType: string, transaction: Transaction) => {
let result: string;
let stage = "init";
const entitySubtype = backlogItemType === "story" ? "story" : "issue";
let transaction: Transaction;
try {
let rolledBack = false;
transaction = await sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE });
let projectSettingsItem: any = await ProjectSettingsDataModel.findOne({
where: { projectId: projectId },
transaction
});
const projectSettingsFindOptions: FindOptions = {
where: { projectId: projectId }
};
if (transaction) {
projectSettingsFindOptions.transaction = transaction;
}
stage = "retrieve project settings";
let projectSettingsItem: any = await ProjectSettingsDataModel.findOne(projectSettingsFindOptions);
if (!projectSettingsItem) {
stage = "retrieve global project settings";
projectSettingsItem = await ProjectSettingsDataModel.findOne({
where: { projectId: null },
transaction
});
}
if (projectSettingsItem) {
stage = "process project settings item";
const projectSettingsItemTyped = mapDbToApiProjectSettings(projectSettingsItem);
const counterSettings = projectSettingsItemTyped.settings.counters[entitySubtype];
const entityNumberPrefix = counterSettings.prefix;
const entityNumberSuffix = counterSettings.suffix;
const counterItem: any = await CounterDataModel.findOne({
where: { entity: "project", entityId: projectId, entitySubtype },
transaction
});
const counterFindOptions: FindOptions = {
where: { entity: "project", entityId: projectId, entitySubtype }
};
if (transaction) {
counterFindOptions.transaction = transaction;
}
stage = "retrieve counter data for project";
const counterItem: any = await CounterDataModel.findOne(counterFindOptions);
if (counterItem) {
stage = "process counter item";
const counterItemTyped = mapDbToApiCounter(counterItem);
counterItemTyped.lastNumber++;
let counterValue = entityNumberPrefix || "";
counterValue += formatNumber(counterItemTyped.lastNumber, counterSettings.totalFixedLength);
counterValue += entityNumberSuffix || "";
counterItemTyped.lastCounterValue = counterValue;
stage = "store new counter value";
await counterItem.update(counterItemTyped);
result = counterItem.lastCounterValue;
}
}
if (!rolledBack) {
await transaction.commit();
}
} catch (err) {
if (transaction) {
await transaction.rollback();
}
throw new Error(`Unable to get new ID value, ${err}`);
throw new Error(`Unable to get new ID value (stage "${stage}"), ${err}`);
}
if (!result) {
throw new Error("Unable to get new ID value - could not retrieve counter item");
Expand All @@ -247,17 +254,17 @@ const getNewCounterValue = async (projectId: string, backlogItemType: string) =>
};

export const backlogItemsPostHandler = async (req: Request, res: Response) => {
const bodyWithId = { ...addIdToBody(req.body) };
if (!bodyWithId.friendlyId) {
const friendlyIdValue = await getNewCounterValue(req.body.projectId, req.body.type);
bodyWithId.friendlyId = friendlyIdValue;
}
const prevBacklogItemId = bodyWithId.prevBacklogItemId;
delete bodyWithId.prevBacklogItemId;
let transaction: Transaction;
try {
let rolledBack = false;
transaction = await sequelize.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE });
const bodyWithId = { ...addIdToBody(req.body) };
if (!bodyWithId.friendlyId) {
const friendlyIdValue = await getNewCounterValue(req.body.projectId, req.body.type, transaction);
bodyWithId.friendlyId = friendlyIdValue;
}
const prevBacklogItemId = bodyWithId.prevBacklogItemId;
delete bodyWithId.prevBacklogItemId;
await sequelize.query('SET CONSTRAINTS "productbacklogitem_backlogitemId_fkey" DEFERRED;', { transaction });
await sequelize.query('SET CONSTRAINTS "productbacklogitem_nextbacklogitemId_fkey" DEFERRED;', { transaction });
const updateBacklogItemPartResult = getUpdatedBacklogItemWhenStatusChanges(null, bodyWithId);
Expand Down
3 changes: 3 additions & 0 deletions src/server/api/handlers/deleters/productBacklogItemDeleter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { buildResponseFromCatchError, buildResponseWithItem } from "../../utils/
import { mapDbToApiProductBacklogItem } from "../../../dataaccess/mappers/dataAccessToApiMappers";

export const removeFromProductBacklog = async (backlogitemId: string, transaction?: Transaction) => {
if (!backlogitemId) {
throw new Error("Unable to remove product backlog item entries using a null/undefined ID");
}
try {
const findItemOptions: FindOptions = buildOptionsWithTransaction({ where: { backlogitemId } }, transaction);
const item = await ProductBacklogItemDataModel.findOne(findItemOptions);
Expand Down
5 changes: 4 additions & 1 deletion src/server/api/handlers/fetchers/backlogItemFetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ export const fetchBacklogItemsByDisplayId = async (
}
};

export const fetchBacklogItems = async (projectId: string | null): Promise<BacklogItemsResult | RestApiErrorResult> => {
export const fetchBacklogItems = async (projectId: string): Promise<BacklogItemsResult | RestApiErrorResult> => {
if (!projectId) {
throw new Error("Unable to retrieve backlog items without specifying a projectId");
}
try {
const params = { projectId };
const options = buildOptionsFromParams(params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ export const productBacklogItemFirstItemInserter = async (
bodyWithId,
transaction: Transaction
): Promise<ProductBacklogItemFirstItemInserterResult> => {
const projectId = bodyWithId.projectId;
if (!projectId) {
throw new Error("Unable to update the product backlog without a projectId");
}
// inserting first item means one of 2 scenarios:
// 1) no items in database yet (add prev = null, next = this new item + add prev = new item, next = null)
// 2) insert before first item (update item's prev to this item, add prev = null, next = this new item)
const firstItems = await ProductBacklogItemDataModel.findAll({ where: { backlogitemId: null }, transaction });
const firstItems = await ProductBacklogItemDataModel.findAll({ where: { backlogitemId: null, projectId }, transaction });
if (!firstItems.length) {
// scenario 1, insert head and tail
await ProductBacklogItemDataModel.create(
Expand Down
21 changes: 7 additions & 14 deletions src/server/api/handlers/sprints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,7 @@ import { StatusCodes } from "http-status-codes";
import { Op } from "sequelize";

// libraries
import {
ApiSprint,
ApiSprintBacklogItem,
DateOnly,
determineSprintStatus,
isoDateStringToDate,
logger,
SprintStatus
} from "@atoll/shared";
import { ApiSprint, DateOnly, determineSprintStatus, logger, SprintStatus } from "@atoll/shared";

// data access
import { sequelize } from "../../dataaccess/connection";
Expand Down Expand Up @@ -95,14 +87,15 @@ export const sprintPatchHandler = async (req: Request, res: Response) => {
if (!sprint) {
respondWithNotFound(res, `Unable to find sprint to patch with ID ${queryParamItemId}`);
} else {
const originalSprint = mapDbToApiSprint(sprint);
const invalidPatchMessage = getInvalidPatchMessage(originalSprint, body);
const invalidPatchMessage = getInvalidPatchMessage(sprint, body);
if (invalidPatchMessage) {
respondWithFailedValidation(res, `Unable to patch: ${invalidPatchMessage}`);
} else {
const newItem = getPatchedItem(originalSprint, body);
await sprint.update(mapApiToDbSprint(newItem));
respondWithItem(res, sprint, originalSprint);
const originalSprint = mapDbToApiSprint(sprint);
const newItem = getPatchedItem(sprint, body);
await sprint.update(newItem);
const apiSprint = mapDbToApiSprint(sprint);
respondWithItem(res, apiSprint, originalSprint);
}
}
} catch (err) {
Expand Down
Loading

0 comments on commit ab476a7

Please sign in to comment.