message: Help2 deleted

diff:
46F21CA92422444600C01DFA /* ScanListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F21CA82422444600C01DFA /* ScanListVC.swift */; };
46F21CAF2422597F00C01DFA /* Scan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F21CAE2422597F00C01DFA /* Scan.swift */; };
46F21CB4242270F700C01DFA /* ScannerStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F21CB3242270F700C01DFA /* ScannerStore.swift */; };
- 46F5CDA324411EDE000089CA /* Help2VC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F5CDA224411EDE000089CA /* Help2VC.swift */; };
5E110F9C2433C4AD0059712A /* UnregisterFinishVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E110F9B2433C4AD0059712A /* UnregisterFinishVC.swift */; };
5EA3C7B52442038E009BBF38 /* HelpMarkdownBackup.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5EA3C7B42442038E009BBF38 /* HelpMarkdownBackup.strings */; };
5EE9D815242F74F5002F833C /* DataHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EE9D814242F74F5002F833C /* DataHeaderCell.swift */; };
46F21CA82422444600C01DFA /* ScanListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanListVC.swift; sourceTree = "<group>"; };
46F21CAE2422597F00C01DFA /* Scan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scan.swift; sourceTree = "<group>"; };
46F21CB3242270F700C01DFA /* ScannerStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannerStore.swift; sourceTree = "<group>"; };
- 46F5CDA224411EDE000089CA /* Help2VC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Help2VC.swift; sourceTree = "<group>"; };
5E110F9B2433C4AD0059712A /* UnregisterFinishVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnregisterFinishVC.swift; sourceTree = "<group>"; };
5EA3C7B42442038E009BBF38 /* HelpMarkdownBackup.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = HelpMarkdownBackup.strings; sourceTree = "<group>"; };
5EE9D814242F74F5002F833C /* DataHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataHeaderCell.swift; sourceTree = "<group>"; };
path = Store;
sourceTree = "<group>";
};
- 46F5CDA124411E37000089CA /* Help 2 */ = {
- isa = PBXGroup;
- children = (
- 46F5CDA224411EDE000089CA /* Help2VC.swift */,
- );
- path = "Help 2";
- sourceTree = "<group>";
- };
5357F8D365121189A6E32250 /* Frameworks */ = {
isa = PBXGroup;
children = (
BD0191452424330200707341 /* Active */,
80ECB96A24292F9600E57601 /* Data List */,
BDB07FBE24254A7B00834E79 /* Help */,
- 46F5CDA124411E37000089CA /* Help 2 */,
46A0D528242CC29A00FAE9EA /* Unregister User */,
46F21CA3242243F400C01DFA /* Debug */,
);
80BB476824238CBB00F552FC /* AccountActivationController.swift in Sources */,
803C5B23242932BF00E49414 /* DataListVC.swift in Sources */,
46A0D52F242CEB8A00FAE9EA /* DeleteDataVC.swift in Sources */,
- 46F5CDA324411EDE000089CA /* Help2VC.swift in Sources */,
8054C0E12420EEFF008907B8 /* Logger.swift in Sources */,
4657C454243BD03D009E0E85 /* NotificationPermissionController.swift in Sources */,
80631A98242CAB5000EB0B6C /* Image.swift in Sources */,

original_message: Help2 deleted


message: Issue 50. Changed CSV file directory

change_type: MODIFY

diff:
@@ -34,8 +34,7 @@ final class CSVMaker: CSVMakering {
private(set) var fromDate: Date?
init?(fromDate: Date?) {
- guard let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first else { return nil }
- self.fileURL = URL(fileURLWithPath: documents).appendingPathComponent("db.csv")
+ self.fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("db.csv")
self.fromDate = fromDate
}

license: MIT License

original_message: Issue 50. Changed CSV file directory


message: Issue 51. PurgeLogs now called in RemoveData and Unregister

diff:
@@ -30,5 +30,6 @@ class DeleteDataVM {
func deleteAllData() {
scannerStore.deleteAllData()
+ FileLogger.shared.purgeLogs()
}
}

diff:
@@ -35,6 +35,7 @@ final class UnregisterUserVC: UIViewController {
return
}
+ FileLogger.shared.purgeLogs()
Log.log("deleteUser request success finished")
AppDelegate.shared.advertiser.stop()


message: Fixed bug with dark mode in help

diff:
@@ -49,6 +49,17 @@ private extension HelpVC {
var helpMarkdown = RemoteValues.helpMarkdown.replacingOccurrences(of: "\\n", with: "\u{0085}")
helpMarkdown = helpMarkdown.replacingOccurrences(of: "([Android](https://github.com/covid19cz/erouska-android), [iOS](https://github.com/covid19cz/erouska-ios))", with: "pro [Android](https://github.com/covid19cz/erouska-android) a [iOS](https://github.com/covid19cz/erouska-ios)")
helpMarkdown = helpMarkdown.replacingOccurrences(of: "[iOS](https://github.com/covid19cz/erouska-ios))", with: "[iOS](https://github.com/covid19cz/erouska-ios) )")
- textView.attributedText = markdownParser.parse(helpMarkdown)
+
+ let attributedText = NSMutableAttributedString(attributedString: markdownParser.parse(helpMarkdown))
+ var textColor: UIColor {
+ if #available(iOS 13.0, *) {
+ return .label
+ } else {
+ return .black
+ }
+ }
+ attributedText.addAttribute(.foregroundColor, value: textColor, range: NSMakeRange(0, attributedText.length))
+
+ textView.attributedText = attributedText
}
}

original_message: Fixed bug with dark mode in help


message: Tmp file db.csv now deleted after sucess/fail uploading

diff:
@@ -160,6 +160,7 @@ final class DataListVC: UIViewController, UITableViewDelegate {
guard let self = self else { return }
self.activityView.isHidden = true
+ self.writer?.deleteFile()
if let error = error {
log("FirebaseUpload: Error \(error.localizedDescription)")

diff:
@@ -18,7 +18,7 @@ protocol CSVMakering {
var fromDate: Date? { get }
func createFile(callback: @escaping Callback)
-
+ func deleteFile()
}
/*
@@ -80,4 +80,11 @@ final class CSVMaker: CSVMakering {
callback(Result(fileURL, metadata), nil)
}
+ func deleteFile() {
+ do {
+ try FileManager.default.removeItem(at: fileURL)
+ } catch let error as NSError {
+ print("Could not clear temp file CSVMaker: \(error)")
+ }
+ }
}


message: Fixed bug with Bluetooth permission in the FirstActivation screen, after installing

diff:
@@ -59,6 +59,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: - Public
func resetAdvertising() {
+ guard KeychainService.BUID != nil else { return }
let wasRunning = advertiser.isRunning
advertiser.stop()
advertiser = BTAdvertiser(

diff:
@@ -62,7 +62,6 @@ final class FirstActivationVC: UIViewController {
}
private extension FirstActivationVC {
-
func setupStrings() {
navigationItem.localizedTitle(viewModel.title)
navigationItem.backBarButtonItem?.localizedTitle(viewModel.back)
@@ -72,7 +71,5 @@ private extension FirstActivationVC {
moreButton.localizedTitle(viewModel.moreButton)
continueButton.localizedTitle(viewModel.continueButton)
howItWorksButton.localizedTitle(viewModel.howItWorksButton)
-
}
-
}


message: no scroll on link click

diff:
@@ -20,6 +20,7 @@ import { ReactComponent as Plus } from "../../assets/plus.svg";
import { ReactComponent as Subtract } from "../../assets/subtract.svg";
import plus from "../../assets/plus.svg";
import subtract from "../../assets/subtract.svg";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
const SelectableCard = ({
iconPath,
@@ -111,6 +112,7 @@ const SelectableCard = ({
onClick={detailsClickWrapper}
className={classNames(styles.link)}
to={ROUTES.PAGE_DETAILS}
+ onKeyUp={spaceKeyHandler}
>
<FormattedMessage
id="selectableCard.details"

diff:
import * as React from "react";
import {KEY_EVENTS} from "./constants"
+// document.documentElement.addEventListener('keydown', (e) => {
+// if (e.key === KEY_EVENTS.SPACE) e.preventDefault();
+// })
+
const spaceKeyHandler = (e: React.KeyboardEvent<HTMLAnchorElement>) => {
- console.log(e.type);
+
let anchor = e.target as HTMLAnchorElement;
+
+ //if pressing space on an anchor, prevent page scroll.
+ anchor.onkeydown = (e) => {
+ if (e.key === KEY_EVENTS.SPACE) e.preventDefault();
+ }
+
if (e.key === KEY_EVENTS.SPACE) {
anchor.click();
- // const x = new MouseEvent('click', {
- // view: window,
- // bubbles: true
- // });
- // e.target.dispatchEvent(x);
}
}

repo: microsoft/webtemplatestudio

original_message: no scroll on link click


message: updated all links

diff:
@@ -10,6 +10,7 @@ import styles from "./styles.module.css";
import { IOption } from "../../types/option";
import { Link } from "react-router-dom";
import { ROUTES } from "../../utils/constants";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
interface IProps {
buttonText: string;
@@ -53,6 +54,7 @@ export const Card = ({
className={styles.details}
to={ROUTES.PAGE_DETAILS}
tabIndex={disabled! ? -1 : 0}
+ onKeyUp={spaceKeyHandler}
>
<FormattedMessage id="card.details" defaultMessage="Details" />
</Link>

diff:
@@ -19,6 +19,7 @@ import {
InjectedIntl,
FormattedMessage
} from "react-intl";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
interface IProps {
detailInfo: IOption;
@@ -41,7 +42,7 @@ const Details = ({
intl
}: IProps) => {
const LinkRenderer = (props: any) => (
-
+
{props.children}

);
@@ -156,6 +157,7 @@ const Details = ({

{licenseObject.text}

diff:
@@ -11,6 +11,7 @@ import { FormattedMessage } from "react-intl";
import { IOption } from "../../types/option";
import { ROUTES } from "../../utils/constants";
import { azureMessages } from "../../mockData/azureServiceOptions";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
interface IProps {
cardTitle: string;
@@ -42,6 +43,7 @@ const LoginCard = ({
<Link
className={styles.details}
onClick={() => handleDetailsClick(option)}
+ onKeyUp={spaceKeyHandler}
to={ROUTES.PAGE_DETAILS}
>
<FormattedMessage id="loginCard.details" defaultMessage="Details" />
@@ -61,6 +63,7 @@ const LoginCard = ({
<a
className={styles.createAccountButton}
href="https://azure.microsoft.com/free/"
+ onKeyUp={spaceKeyHandler}
>
<FormattedMessage
id="loginCard.createAccount"

diff:
@@ -3,6 +3,7 @@ import * as React from "react";
import { Link } from "react-router-dom";
import styles from "./styles.module.css";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
const TopNavBarLink = ({
pageNumber,
@@ -31,6 +32,7 @@ const TopNavBarLink = ({
to={path}
onClick={handleClick}
className={styles.container}
+ onKeyUp={spaceKeyHandler}
>
<div
className={classnames(styles.text, {

diff:
@@ -6,6 +6,7 @@ import { getVersionsSelector } from "../../selectors/vscodeApiSelector";
import { IVersions } from "../../types/version";
import { defineMessages, InjectedIntlProps, injectIntl } from "react-intl";
import { AppState } from "../../reducers";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
interface IStateProps {
versions: IVersions;
@@ -38,6 +39,7 @@ const About = ({ versions, intl }: Props) => {
<a
className={styles.link}
href="https://github.com/Microsoft/WebTemplateStudio"
+ onKeyUp={spaceKeyHandler}
>
Web Template Studio
</a>

diff:
@@ -42,6 +42,7 @@ import { getVSCodeApiSelector } from "../../selectors/vscodeApiSelector";
import RootAction from "../../actions/ActionType";
import { messages } from "./messages";
import classNames from "classnames";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
const DEFAULT_VALUE = {
value: "Select...",
@@ -335,6 +336,8 @@ const AzureFunctionsResourceModal = (props: Props) => {
tabIndex={disabled ? -1 : 0}
className={styles.link}
href={links[formSectionId]}
+ onKeyUp={spaceKeyHandler}
+
>
{rightHeader}
</a>

diff:
@@ -25,6 +25,7 @@ import { withLocalPath } from "../../utils/getSvgUrl";
import { AppState } from "../../reducers";
import { Dispatch } from "redux";
import RootAction from "../../actions/ActionType";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
interface IDispatchProps {
setDetailPage: (detailPageInfo: IOption) => any;
@@ -73,6 +74,7 @@ class AzureLogin extends React.Component<Props> {
tabIndex={0}
to={ROUTES.REVIEW_AND_GENERATE}
className={styles.optionalFlag}
+ onKeyUp={spaceKeyHandler}
>
{azureMessages.azureSkipButton.defaultMessage}
</Link>

diff:
@@ -42,6 +42,7 @@ import { ThunkDispatch } from "redux-thunk";
import RootAction from "../../actions/ActionType";
import { messages } from "./messages";
import classNames from "classnames";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
const DEFAULT_VALUE = {
value: "Select...",
@@ -321,6 +322,7 @@ const CosmosResourceModal = (props: Props) => {
tabIndex={disabled! ? -1 : 0}
className={styles.link}
href={links[formSectionId]}
+ onKeyUp={spaceKeyHandler}
>
{rightHeader}
</a>

diff:
@@ -26,6 +26,7 @@ import { getOutputPath } from "../../selectors/wizardSelectionSelector";
import { strings as messages } from "./strings";
import { resetWizardAction } from "../../actions/wizardInfoActions/resetWizardAction";
import { MODAL_TYPES } from "../../actions/modalActions/typeKeys";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
interface LinksDict {
[serviceId: string]: string;
@@ -73,7 +74,7 @@ const PostGenerationModal = ({
}: Props) => {
const { formatMessage } = intl;
const LinkRenderer = (props: any) => (
-
+
{props.children}

);
@@ -221,6 +222,7 @@ const PostGenerationModal = ({

{formatMessage(messages.help)}

diff:
@@ -12,6 +12,7 @@ import { defineMessages, InjectedIntlProps, injectIntl } from "react-intl";
import { ThunkDispatch } from "redux-thunk";
import { AppState } from "../../reducers";
import RootAction from "../../actions/ActionType";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
interface IDispatchProps {
openViewLicensesModal: () => any;
@@ -65,6 +66,7 @@ const ReviewAndGenerate = (props: Props) => {
rel="noopener noreferrer"
className={styles.link}
href="https://github.com/Microsoft/WebTemplateStudio/issues"
+ onKeyUp={spaceKeyHandler}
>
{formatMessage(messages.giveFeedback)}
</a>

diff:
@@ -42,6 +42,7 @@ import { IOption } from "../../types/option";
import { IVSCodeObject } from "../../reducers/vscodeApiReducer";
import { getVSCodeApiSelector } from "../../selectors/vscodeApiSelector";
import { isValidNameAndProjectPathSelector } from "../../selectors/wizardSelectionSelector";
+import spaceKeyHandler from "../../utils/spaceKeyHandler";
interface IDispatchProps {
selectBackendFramework: (framework: ISelected) => void;
@@ -224,6 +225,7 @@ class RightSidebar extends React.Component<Props, IRightSidebarState> {
<a
target="_blank"
rel="noopener noreferrer"
+ onKeyDown={spaceKeyHandler}
className={styles.link}
href="https://github.com/Microsoft/WebTemplateStudio/issues"
>

diff:
"details.none",
"draggableSidebarItem.changeItemName",
"loginCard.details",
- "loginCard.createAccount",
"loginCard.signIn",
+ "loginCard.createAccount",
"outputPath.outputPath",
"outputPath.ariaOutputPath",
"selectableCard.details",
"topNavBar.ariaNavLabel",
"topNavBar.frameworks",
"topNavBar.services",
- "topNavBar.pages",
"topNavBar.summary",
"topNavBar.newProject",
"about.templatesVersion",
"azureFunctionsModal.ariaNumFunctionsLabel",
"azureFunctionsModal.appNameLabel",
"azureFunctionsModal.ariaAppNameLabel",
- "azureFunctionsModal.appName",
"azureFunctionsModal.createFunctionApp",
"azureFunctionsModal.appNameSubLabel",
"azureFunctionsModal.runtimeStackSubLabel",
"azureSubscriptions.azureCosmosBody",
"azureSubscriptions.azureFunctions",
"azureSubscriptions.cosmosResource",
+ "hostingServices.title",
+ "storageServices.title",
"cosmosDBSelection.edit",
"cosmosResourceModule.apiLabel",
"cosmosResourceModule.ariaApiLabel",
"cosmosResourceModule.createCosmosRes",
"cosmosResourceModule.internalName",
"cosmosResourceModule.accountNameSubLabel",
- "cosmosResourceModule.locationSubLabel",
- "cosmosResourceModule.apiSubLabel",
"dependencyChecker.installedMessage",
"dependencyChecker.notInstalledMessage",
"dependencyChecker.iconAltMessage",
"sortablePageList.pages",
"summarySection.duplicateName",
"viewLicensesModal.closeModalLabel",
+ "azureSkip.buttonTitle",
"azureLogin.azureTitle",
"azureLogin.azureCardBody",
"azureLogin.longDescription",
"azureLogin.azureLoginTitle",
+ "azureLogin.azureLoginInfo",
"azureFunctions.Title",
"azureFunctions.cardBody",
"cosmosDb.title",
"cosmosDb.longDescription",
"cosmosDb.cardBody",
"azureModal.chooseExisting",
- "azureModal.createNewResourceGroupDisplayMessage",
- "azureModal.createNewResourceGroupSelectedDisplayMessage",
"azureModal.subscriptionLabel",
"azureModal.ariaSubscriptionLabel",
"azureModal.resourceGroupLabel",

original_message: updated all links


message: keyup not keydown!

diff:
@@ -225,7 +225,7 @@ class RightSidebar extends React.Component<Props, IRightSidebarState> {
<a
target="_blank"
rel="noopener noreferrer"
- onKeyDown={spaceKeyHandler}
+ onKeyUp={spaceKeyHandler}
className={styles.link}
href="https://github.com/Microsoft/WebTemplateStudio/issues"
>

original_message: keyup not keydown!


message: Update whitelist_en.json

diff:
"azureFunctionsModal.ariaNumFunctionsLabel",
"azureFunctionsModal.appNameLabel",
"azureFunctionsModal.ariaAppNameLabel",
+ "azureFunctionsModal.appName",
"azureFunctionsModal.createFunctionApp",
"azureFunctionsModal.appNameSubLabel",
"azureFunctionsModal.runtimeStackSubLabel",
"cosmosResourceModule.createCosmosRes",
"cosmosResourceModule.internalName",
"cosmosResourceModule.accountNameSubLabel",
+ "cosmosResourceModule.locationSubLabel",
+ "cosmosResourceModule.apiSubLabel",
"dependencyChecker.installedMessage",
"dependencyChecker.notInstalledMessage",
"dependencyChecker.iconAltMessage",
"cosmosDb.longDescription",
"cosmosDb.cardBody",
"azureModal.chooseExisting",
+ "azureModal.createNewResourceGroupDisplayMessage",
+ "azureModal.createNewResourceGroupSelectedDisplayMessage",
"azureModal.subscriptionLabel",
"azureModal.ariaSubscriptionLabel",
"azureModal.resourceGroupLabel",

original_message: Update whitelist_en.json


message: updated name and cleanup

diff:
@@ -10,7 +10,7 @@ import styles from "./styles.module.css";
import { IOption } from "../../types/option";
import { Link } from "react-router-dom";
import { ROUTES } from "../../utils/constants";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface IProps {
buttonText: string;
@@ -52,7 +52,7 @@ export const Card = ({
className={styles.details}
to={ROUTES.PAGE_DETAILS}
tabIndex={disabled! ? -1 : 0}
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
>
<FormattedMessage id="card.details" defaultMessage="Details" />
</Link>

diff:
@@ -19,7 +19,7 @@ import {
InjectedIntl,
FormattedMessage
} from "react-intl";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface IProps {
detailInfo: IOption;
@@ -42,7 +42,7 @@ const Details = ({
intl
}: IProps) => {
const LinkRenderer = (props: any) => (
-
+
{props.children}

);
@@ -93,7 +93,7 @@ const Details = ({
role="button"
tabIndex={0}
onClick={handleBackClick}
- onKeyUp={keyDownHandler}
+ onKeyDown={keyDownHandler}
className={styles.innerBackContainer}
>
{backArrow && <BackArrow className={styles.backIcon} />}
@@ -157,7 +157,7 @@ const Details = ({

{licenseObject.text}

diff:
@@ -11,7 +11,7 @@ import { FormattedMessage } from "react-intl";
import { IOption } from "../../types/option";
import { ROUTES } from "../../utils/constants";
import { azureMessages } from "../../mockData/azureServiceOptions";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface IProps {
cardTitle: string;
@@ -43,7 +43,7 @@ const LoginCard = ({
<Link
className={styles.details}
onClick={() => handleDetailsClick(option)}
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
to={ROUTES.PAGE_DETAILS}
>
<FormattedMessage id="loginCard.details" defaultMessage="Details" />
@@ -63,7 +63,7 @@ const LoginCard = ({
<a
className={styles.createAccountButton}
href="https://azure.microsoft.com/free/"
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
>
<FormattedMessage
id="loginCard.createAccount"

diff:
@@ -20,7 +20,7 @@ import { ReactComponent as Plus } from "../../assets/plus.svg";
import { ReactComponent as Subtract } from "../../assets/subtract.svg";
import plus from "../../assets/plus.svg";
import subtract from "../../assets/subtract.svg";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
const SelectableCard = ({
iconPath,
@@ -112,7 +112,7 @@ const SelectableCard = ({
onClick={detailsClickWrapper}
className={classNames(styles.link)}
to={ROUTES.PAGE_DETAILS}
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
>
<FormattedMessage
id="selectableCard.details"

diff:
@@ -6,7 +6,7 @@ import { injectIntl, FormattedMessage, InjectedIntl } from "react-intl";
import { Link } from "react-router-dom";
import styles from "./styles.module.css";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
import { ARIA_LABELS_NAVIGATION } from "../../utils/constants";
const TopNavBarLink = ({
@@ -38,7 +38,7 @@ const TopNavBarLink = ({
to={path}
onClick={handleClick}
className={styles.container}
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
>
<div
className={classnames(styles.text, {

diff:
@@ -6,7 +6,7 @@ import { getVersionsSelector } from "../../selectors/vscodeApiSelector";
import { IVersions } from "../../types/version";
import { defineMessages, InjectedIntlProps, injectIntl } from "react-intl";
import { AppState } from "../../reducers";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface IStateProps {
versions: IVersions;
@@ -39,7 +39,7 @@ const About = ({ versions, intl }: Props) => {
<a
className={styles.link}
href="https://github.com/Microsoft/WebTemplateStudio"
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
>
Web Template Studio
</a>

diff:
@@ -42,7 +42,7 @@ import { getVSCodeApiSelector } from "../../selectors/vscodeApiSelector";
import RootAction from "../../actions/ActionType";
import { messages } from "./messages";
import classNames from "classnames";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
const DEFAULT_VALUE = {
value: "Select...",
@@ -336,7 +336,7 @@ const AzureFunctionsResourceModal = (props: Props) => {
tabIndex={disabled ? -1 : 0}
className={styles.link}
href={links[formSectionId]}
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
>
{rightHeader}

diff:
@@ -25,7 +25,7 @@ import { withLocalPath } from "../../utils/getSvgUrl";
import { AppState } from "../../reducers";
import { Dispatch } from "redux";
import RootAction from "../../actions/ActionType";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface IDispatchProps {
setDetailPage: (detailPageInfo: IOption) => any;
@@ -74,7 +74,7 @@ class AzureLogin extends React.Component<Props> {
tabIndex={0}
to={ROUTES.REVIEW_AND_GENERATE}
className={styles.optionalFlag}
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
>
{azureMessages.azureSkipButton.defaultMessage}
</Link>

diff:
@@ -42,7 +42,7 @@ import { ThunkDispatch } from "redux-thunk";
import RootAction from "../../actions/ActionType";
import { messages } from "./messages";
import classNames from "classnames";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
const DEFAULT_VALUE = {
value: "Select...",
@@ -322,7 +322,7 @@ const CosmosResourceModal = (props: Props) => {
tabIndex={disabled! ? -1 : 0}
className={styles.link}
href={links[formSectionId]}
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
>
{rightHeader}
</a>

diff:
@@ -50,7 +50,7 @@ import { IFunctionName } from "../AzureFunctionsSelection";
import { ReactComponent as NextArrow } from "../../assets/nextarrow.svg";
import nextArrow from "../../assets/nextarrow.svg";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface IDispatchProps {
setRouteVisited: (route: string) => void;
@@ -226,7 +226,7 @@ class Footer extends React.Component<Props> {
onClick={event => {
this.handleLinkBackClick(event, pathname);
}}
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
to={
pathsBack[pathname] === undefined
? ROUTES.NEW_PROJECT
@@ -251,7 +251,7 @@ class Footer extends React.Component<Props> {
onClick={event => {
this.handleLinkClick(event, pathname);
}}
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
to={pathsNext[pathname]}
>
<FormattedMessage id="footer.next" defaultMessage="Next" />

diff:
@@ -15,7 +15,7 @@ import {
} from "../../selectors/wizardNavigationSelector";
import { AppState } from "../../reducers";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface IStateProps {
frameworkLicenses: string[];
@@ -40,7 +40,7 @@ const Licenses = ({
}: Props) => {
const LinkRenderer = (props: any) => {
return (
-
+
{props.children}

);

diff:
@@ -26,7 +26,7 @@ import { getOutputPath } from "../../selectors/wizardSelectionSelector";
import { strings as messages } from "./strings";
import { resetWizardAction } from "../../actions/wizardInfoActions/resetWizardAction";
import { MODAL_TYPES } from "../../actions/modalActions/typeKeys";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface LinksDict {
[serviceId: string]: string;
@@ -74,7 +74,7 @@ const PostGenerationModal = ({
}: Props) => {
const { formatMessage } = intl;
const LinkRenderer = (props: any) => (
-
+
{props.children}

);
@@ -222,7 +222,7 @@ const PostGenerationModal = ({

{formatMessage(messages.help)}

diff:
@@ -12,7 +12,7 @@ import { defineMessages, InjectedIntlProps, injectIntl } from "react-intl";
import { ThunkDispatch } from "redux-thunk";
import { AppState } from "../../reducers";
import RootAction from "../../actions/ActionType";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface IDispatchProps {
openViewLicensesModal: () => any;
@@ -66,7 +66,7 @@ const ReviewAndGenerate = (props: Props) => {
rel="noopener noreferrer"
className={styles.link}
href="https://github.com/Microsoft/WebTemplateStudio/issues"
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
>
{formatMessage(messages.giveFeedback)}
</a>

diff:
@@ -42,7 +42,7 @@ import { IOption } from "../../types/option";
import { IVSCodeObject } from "../../reducers/vscodeApiReducer";
import { getVSCodeApiSelector } from "../../selectors/vscodeApiSelector";
import { isValidNameAndProjectPathSelector } from "../../selectors/wizardSelectionSelector";
-import spaceKeyHandler from "../../utils/spaceKeyHandler";
+import keyUpHandler from "../../utils/keyUpHandler";
interface IDispatchProps {
selectBackendFramework: (framework: ISelected) => void;
@@ -225,7 +225,7 @@ class RightSidebar extends React.Component<Props, IRightSidebarState> {
<a
target="_blank"
rel="noopener noreferrer"
- onKeyUp={spaceKeyHandler}
+ onKeyUp={keyUpHandler}
className={styles.link}
href="https://github.com/Microsoft/WebTemplateStudio/issues"
>

change_type: RENAME

diff:
import * as React from "react";
import {KEY_EVENTS} from "./constants"
-// document.documentElement.addEventListener('keydown', (e) => {
-// if (e.key === KEY_EVENTS.SPACE) e.preventDefault();
-// })
+const keyUpHandler = (e: React.KeyboardEvent<HTMLAnchorElement>) => {
-const spaceKeyHandler = (e: React.KeyboardEvent<HTMLAnchorElement>) => {
-
- let anchor = e.target as HTMLAnchorElement;
+ const anchor = e.target as HTMLAnchorElement;
//if pressing space on an anchor, prevent page scroll.
anchor.onkeydown = (e) => {
@@ -19,4 +15,4 @@ const spaceKeyHandler = (e: React.KeyboardEvent<HTMLAnchorElement>) => {
}
}
-export default spaceKeyHandler;
\ No newline at end of file
+export default keyUpHandler;
\ No newline at end of file

original_message: updated name and cleanup


message: fixed bug around name length

diff:
@@ -5,6 +5,7 @@ import styles from "./styles.module.css";
interface IProps {
handleChange: (e: React.SyntheticEvent<HTMLInputElement>) => any;
+ handleKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => any;
ariaLabel: string;
value?: string;
customStyle?: string;
@@ -20,12 +21,14 @@ const Input = ({
customStyle,
placeholder,
maxLength,
+ handleKeyDown,
autoFocus
}: IProps) => {
return (
<input
aria-label={ariaLabel}
onChange={handleChange}
+ onKeyDown={handleKeyDown}
placeholder={placeholder}
className={classnames(styles.input, customStyle)}
value={value}

diff:
@@ -60,6 +60,13 @@ const messages = defineMessages({
id: "projectName.ariaProjectName",
defaultMessage: "Project Name Input"
},
+ nameTooLong: {
+ id: "projectNameError.nameTooLong",
+ defaultMessage:
+ "Project name can only be " +
+ PROJECT_NAME_CHARACTER_LIMIT +
+ " characters long"
+ },
outputPathTitle: {
id: "projectName.outputPathTitle",
defaultMessage: "Save To"
@@ -67,6 +74,8 @@ const messages = defineMessages({
});
const ProjectNameAndOutput = (props: Props) => {
+ const [projectNameMaxLength, setprojectNameMaxLength] = React.useState(false);
+
React.useEffect(() => {
if (props.vscode) {
if (props.projectPathValidation) {
@@ -97,6 +106,18 @@ const ProjectNameAndOutput = (props: Props) => {
const element = e.currentTarget as HTMLInputElement;
props.updateProjectName(element.value);
};
+ const validateKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ const element = e.target as HTMLInputElement;
+ const inputKeyCheck = /^[A-Za-z0-9_\- ]$/
+
+ if(element.value.length === 50 && inputKeyCheck.test(e.key)){
+ setprojectNameMaxLength(true);
+ e.stopPropagation();
+ }else{
+ setprojectNameMaxLength(false);
+ }
+ }
+
const handleOutputPathChange = (
e: React.SyntheticEvent<HTMLInputElement>
) => {
@@ -118,6 +139,7 @@ const ProjectNameAndOutput = (props: Props) => {

<Input
handleChange={handleProjectNameChange}
+ handleKeyDown={validateKey}
ariaLabel={props.intl.formatMessage(messages.ariaProjectNameLabel)}
value={props.projectName}
maxLength={PROJECT_NAME_CHARACTER_LIMIT}
@@ -129,6 +151,12 @@ const ProjectNameAndOutput = (props: Props) => {
.error as FormattedMessage.MessageDescriptor)}

)}
+ {projectNameMaxLength && (
+

+ {props.intl.formatMessage(messages
+ .nameTooLong as FormattedMessage.MessageDescriptor)}
+

+ )}

diff:
@@ -15,13 +15,6 @@ const messages = defineMessages({
defaultMessage:
"Name may only contain letters, numbers, spaces, dashes or underscores"
},
- nameTooLong: {
- id: "projectNameError.nameTooLong",
- defaultMessage:
- "Name should be under " +
- PROJECT_NAME_CHARACTER_LIMIT +
- " characters long"
- },
nameStartLetter: {
id: "pageNameError.nameStartLetter",
defaultMessage: "Name may only start with letters"
@@ -68,10 +61,6 @@ export function validateName(title: string, type: string): any {
error = messages.invalidProjectName;
}
}
- if (title.length >= PROJECT_NAME_CHARACTER_LIMIT) {
- isValid = false;
- error = messages.nameTooLong;
- }
} else if (type === "function") {
if (!/^[A-Za-z0-9][A-Za-z0-9-]*[a-zA-Z0-9]$/.test(title)) {
isValid = false;

original_message: fixed bug around name length


message: updated constant and removed unused package

diff:
@@ -63,9 +63,7 @@ const messages = defineMessages({
nameTooLong: {
id: "projectNameError.nameTooLong",
defaultMessage:
- "Project name can only be " +
- PROJECT_NAME_CHARACTER_LIMIT +
- " characters long"
+ `Project name can only be {maxLength} characters long`
},
outputPathTitle: {
id: "projectName.outputPathTitle",
@@ -157,7 +155,9 @@ const ProjectNameAndOutput = (props: Props) => {
{projectNameMaxLength && (
<div className={styles.errorMessage}>
{props.intl.formatMessage(messages
- .nameTooLong as FormattedMessage.MessageDescriptor)}
+ .nameTooLong as FormattedMessage.MessageDescriptor,
+ {maxLength: PROJECT_NAME_CHARACTER_LIMIT}
+ )}
</div>
)}
</div>

diff:
import { defineMessages, FormattedMessage } from "react-intl";
-import { PROJECT_NAME_CHARACTER_LIMIT } from "./constants";
const messages = defineMessages({
duplicateName: {


message: linting and void

diff:
@@ -5,7 +5,7 @@ import styles from "./styles.module.css";
interface IProps {
handleChange: (e: React.SyntheticEvent<HTMLInputElement>) => any;
- handleKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => any;
+ handleKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
ariaLabel: string;
value?: string;
customStyle?: string;

original_message: linting and void


message:
Added Character Limit on Page Names
added char limit on page names

diff:
@@ -20,6 +20,10 @@ const messages = defineMessages({
changeItemName: {
id: "draggableSidebarItem.changeItemName",
defaultMessage: "Change Item Name"
+ },
+ pageNameMaxLength: {
+ id: "draggableSidebarItem.pageNameMaxLength",
+ defaultMessage: "Page name must be under {maxLength} characters long. "
}
});
@@ -36,6 +40,7 @@ const DraggableSidebarItem = ({
reorderSvgUrl,
itemTitle,
handleInputChange,
+ maxInputLength,
idx,
azureFunctionName,
withIndent,
@@ -55,6 +60,7 @@ const DraggableSidebarItem = ({
closeSvgUrl: string;
itemTitle?: string;
handleInputChange?: (e: any, idx: number) => void;
+ maxInputLength?: number;
idx?: number;
azureFunctionName?: IFunctionName;
withIndent?: boolean;
@@ -65,6 +71,7 @@ const DraggableSidebarItem = ({
isAzureFunction?: boolean;
totalPageCount?: number;
}) => {
+ const [pageNameMaxLength, setPageNameMaxLength] = React.useState(false);
const handleKeyDown = (event: React.KeyboardEvent<SVGSVGElement>) => {
if (event.key === KEY_EVENTS.ENTER || event.key === KEY_EVENTS.SPACE) {
handleCloseOnClick();
@@ -73,6 +80,17 @@ const DraggableSidebarItem = ({
const handleCloseOnClick = () => {
idx && handleCloseClick && handleCloseClick(idx - 1); // correction for idx + 1 to prevent 0th falsey behaviour
};
+
+ const handleKeyDownInput = (e: React.KeyboardEvent<HTMLInputElement>) => {
+ const inputKeyCheck = /^[A-Za-z0-9_\- ]$/
+ const event = e.target as HTMLInputElement;
+ if (event.value.length === maxInputLength && inputKeyCheck.test(e.key)) {
+ setPageNameMaxLength(true);
+ e.stopPropagation();
+ } else {
+ setPageNameMaxLength(false);
+ }
+ }
return (
<div>
{itemTitle && (
@@ -116,12 +134,14 @@ const DraggableSidebarItem = ({
className={classnames(styles.input, {
[styles.azureFunctionNameInput]: isAzureFunction
})}
+ maxLength={maxInputLength}
value={page ? page.title : azureFunctionName!.title}
onChange={e => {
if (handleInputChange && idx) {
handleInputChange(e.target.value, idx - 1);
}
}}
+ onKeyDown={handleKeyDownInput}
/>
) : (
<input
@@ -136,6 +156,19 @@ const DraggableSidebarItem = ({
)}
</div>
</div>
+ {pageNameMaxLength && (<div
+ className={classnames({
+ [styles.errorTextContainer]:
+ withIndent || reorderSvgUrl || true,
+ [styles.textContainer]: !withIndent,
+ [styles.largeIndentContainer]: withLargeIndent
+ })}
+ >
+ {intl.formatMessage(messages
+ .pageNameMaxLength,
+ { maxLength: maxInputLength }
+ )}
+ </div>)}
{((page && !page.isValidTitle) ||
(azureFunctionName && !azureFunctionName.isValidTitle)) && (
<div

diff:
@@ -18,11 +18,13 @@ const SortableSidebarItem = SortableElement(
idx,
handleInputChange,
handleCloseClick,
- totalPageCount
+ totalPageCount,
+ maxInputLength
}: {
page: any;
idx: number;
handleInputChange: any;
+ maxInputLength?: number;
handleCloseClick?: (idx: number) => void;
totalPageCount: number;
}) => {
@@ -34,6 +36,7 @@ const SortableSidebarItem = SortableElement(
reorderSvgUrl={withLocalPath(reorder)}
handleInputChange={handleInputChange}
handleCloseClick={handleCloseClick}
+ maxInputLength={maxInputLength}
idx={idx + 1}
totalPageCount={totalPageCount}
/>
@@ -88,11 +91,13 @@ const SortableList = SortableContainer(
pages,
isSummaryPage,
handleInputChange,
- handleCloseClick
+ handleCloseClick,
+ maxInputLength
}: {
pages: ISelected[];
isSummaryPage?: boolean;
handleInputChange: any;
+ maxInputLength?: number;
handleCloseClick?: (idx: number) => void;
}) => {
const totalPageCount = pages.length;
@@ -108,6 +113,7 @@ const SortableList = SortableContainer(
idx={idx}
page={page}
handleInputChange={handleInputChange}
+ maxInputLength={maxInputLength}
handleCloseClick={handleCloseClick}
totalPageCount={totalPageCount}
/>

diff:
@@ -21,7 +21,7 @@ import { AppState } from "../../reducers";
import { Dispatch } from "redux";
import RootAction from "../../actions/ActionType";
-const MAX_PAGE_NAME_LENGTH = 50;
+import { PAGE_NAME_CHARACTER_LIMIT } from "../../utils/constants"
interface ISortablePageListProps {
selectedPages: any[];
@@ -86,10 +86,8 @@ const SortablePageList = (props: Props) => {
break;
}
}
- if (pages[idx].title.length < MAX_PAGE_NAME_LENGTH) {
setPages(pages);
props.selectPages(pages);
- }
};
const onSortEnd = ({
oldIndex,
@@ -138,6 +136,7 @@ const SortablePageList = (props: Props) => {
<SortableList
pages={selectedPages}
isSummaryPage={isSummaryPage}
+ maxInputLength={PAGE_NAME_CHARACTER_LIMIT}
onSortEnd={onSortEnd}
distance={DRAG_PIXEL_THRESHOLD}
handleInputChange={handleInputChange}

diff:
@@ -9,6 +9,8 @@ const NEW_PROJECT = "/";
const PROJECT_NAME_CHARACTER_LIMIT = 50;
+const PAGE_NAME_CHARACTER_LIMIT = 50;
+
const MAX_PAGES_ALLOWED = 20;
const WEB_TEMPLATE_STUDIO_LINKS = {
@@ -151,6 +153,7 @@ export {
COSMOS_APIS,
DEVELOPMENT,
PROJECT_NAME_CHARACTER_LIMIT,
+ PAGE_NAME_CHARACTER_LIMIT,
MAX_PAGES_ALLOWED,
WEB_TEMPLATE_STUDIO_LINKS,
FRAMEWORK_TYPE,

original_message:
Added Character Limit on Page Names (#940)

added char limit on page names


diff:
@@ -46,7 +46,7 @@ export default {
WarningMessage
},
- data: function() {
+ data() {
return {
gridTextAssets: [
{
@@ -60,12 +60,12 @@ export default {
};
},
- created: function() {
+ created() {
this.fetchTextAssets();
},
methods: {
- fetchTextAssets: function() {
+ fetchTextAssets() {
fetch(CONSTANTS.ENDPOINT.GRID)
.then(response => {
if (!response.ok) {
@@ -83,7 +83,7 @@ export default {
} ${error}`;
});
},
- handleWarningClose: function() {
+ handleWarningClose() {
this.WarningMessageOpen = false;
this.WarningMessageText = "";
}

diff:
@@ -37,7 +37,7 @@ export default {
WarningMessage
},
- data: function() {
+ data() {
return {
list: [],
textField: "",
@@ -46,12 +46,12 @@ export default {
};
},
- created: function() {
+ created() {
this.fetchTextAssets();
},
methods: {
- fetchTextAssets: function() {
+ fetchTextAssets() {
fetch(CONSTANTS.ENDPOINT.LIST)
.then(response => {
if (!response.ok) {
@@ -67,7 +67,7 @@ export default {
} ${error}`;
});
},
- handleAddListItem: function() {
+ handleAddListItem() {
// Warning Pop Up if the user submits an empty message
if (!this.textField) {
this.WarningMessageOpen = true;

diff:
@@ -39,7 +39,7 @@ export default {
WarningMessage
},
- data: function() {
+ data() {
return {
masterDetailText: [
{
@@ -55,12 +55,12 @@ export default {
};
},
- created: function() {
+ created() {
this.fetchTextAssets();
},
methods: {
- fetchTextAssets: function() {
+ fetchTextAssets() {
fetch(CONSTANTS.ENDPOINT.MASTERDETAIL)
.then(response => {
if (!response.ok) {
@@ -78,11 +78,11 @@ export default {
} ${error}`;
});
},
- handleWarningClose: function() {
+ handleWarningClose() {
this.WarningMessageOpen = false;
this.WarningMessageText = "";
},
- handleDisplayTabClick: function(id) {
+ handleDisplayTabClick(id) {
this.currentDisplayTabIndex = id;
}
}

original_message: used object short hands wherever possible (#986)


message: updated to use shorthands

diff:
<div class="row justify-content-around text-center pb-5">
<GridComponent
v-for="textAsset in gridTextAssets"
- v-bind:key="textAsset.id"
- v-bind:header="textAsset.title"
- v-bind:description="textAsset.shortDescription"
+ :key="textAsset.id"
+ :header="textAsset.title"
+ :description="textAsset.shortDescription"
/>
</div>
</div>
</main>
<WarningMessage
v-if="WarningMessageOpen"
- v-on:onWarningClose="handleWarningClose"
- v-bind:text="WarningMessageText"
+ @onWarningClose="handleWarningClose"
+ :text="WarningMessageText"
/>
</div>
</template>
@@ -78,9 +78,7 @@ export default {
})
.catch(error => {
this.WarningMessageOpen = true;
- this.WarningMessageText = `${
- CONSTANTS.ERROR_MESSAGE.GRID_GET
- } ${error}`;
+ this.WarningMessageText = `${CONSTANTS.ERROR_MESSAGE.GRID_GET} ${error}`;
});
},
handleWarningClose() {

diff:
class="close py-2"
data-dismiss="alert"
aria-label="Close"
- v-on:click="$emit('onDeleteListItem', listItem)"
+ @click="$emit('onDeleteListItem', listItem)"
>

×

</button>

diff:
Bootstrap VueList Template

- <ListForm v-on:onAddListItem="handleAddListItem" v-model="textField"/>
+ <ListForm @onAddListItem="handleAddListItem" v-model="textField" />

<ListItem
v-for="listItem in list"
- v-bind:key="listItem._id"
- v-bind:listItem="listItem"
- v-on:onDeleteListItem="handleDeleteListItem"
+ :key="listItem._id"
+ :listItem="listItem"
+ @onDeleteListItem="handleDeleteListItem"
/>
<WarningMessage
v-if="WarningMessageOpen"
- v-on:onWarningClose="handleWarningClose"
- v-bind:text="WarningMessageText"
+ @onWarningClose="handleWarningClose"
+ :text="WarningMessageText"
/>

@@ -62,9 +62,7 @@ export default {
.then(result => (this.list = result))
.catch(error => {
this.WarningMessageOpen = true;
- this.WarningMessageText = `${
- CONSTANTS.ERROR_MESSAGE.LIST_GET
- } ${error}`;
+ this.WarningMessageText = `${CONSTANTS.ERROR_MESSAGE.LIST_GET} ${error}`;
});
},
handleAddListItem() {
@@ -94,9 +92,7 @@ export default {
})
.catch(error => {
this.WarningMessageOpen = true;
- this.WarningMessageText = `${
- CONSTANTS.ERROR_MESSAGE.LIST_ADD
- } ${error}`;
+ this.WarningMessageText = `${CONSTANTS.ERROR_MESSAGE.LIST_ADD} ${error}`;
});
},
handleDeleteListItem(listItem) {
@@ -112,9 +108,7 @@ export default {
})
.catch(error => {
this.WarningMessageOpen = true;
- this.WarningMessageText = `${
- CONSTANTS.ERROR_MESSAGE.LIST_DELETE
- } ${error}`;
+ this.WarningMessageText = `${CONSTANTS.ERROR_MESSAGE.LIST_DELETE} ${error}`;
});
},
handleWarningClose() {

diff:
<div class="list-group list-group-flush border-bottom">
<MasterDetailSideBarTab
v-for="(textAssets, index) in masterDetailText"
- v-on:onDisplayTabClick="handleDisplayTabClick"
- v-bind:tabText="textAssets.title"
- v-bind:index="index"
- v-bind:key="textAssets.id"
+ @onDisplayTabClick="handleDisplayTabClick"
+ :tabText="textAssets.title"
+ :index="index"
+ :key="textAssets.id"
/>
</div>
</div>
- <MasterDetailPage v-bind:textSampleData="masterDetailText[currentDisplayTabIndex]"/>
+ <MasterDetailPage :textSampleData="masterDetailText[currentDisplayTabIndex]" />
</div>
</main>
<WarningMessage
v-if="WarningMessageOpen"
- v-on:onWarningClose="handleWarningClose"
- v-bind:text="WarningMessageText"
+ @onWarningClose="handleWarningClose"
+ :text="WarningMessageText"
/>
</div>
</template>
@@ -73,9 +73,7 @@ export default {
})
.catch(error => {
this.WarningMessageOpen = true;
- this.WarningMessageText = `${
- CONSTANTS.ERROR_MESSAGE.MASTERDETAIL_GET
- } ${error}`;
+ this.WarningMessageText = `${CONSTANTS.ERROR_MESSAGE.MASTERDETAIL_GET} ${error}`;
});
},
handleWarningClose() {

original_message: updated to use shorthands (#984)


message:
VueJS: updated through use of props.
better use of vue props

diff:
export default {
name: "GridComponent",
props: {
- header: String,
- description: String
+ header: {
+ type: String,
+ required: true
+ },
+ description: {
+ type: String,
+ required: true
+ }
}
};
</script>

diff:
@@ -50,8 +50,8 @@ export default {
return {
gridTextAssets: [
{
- description: "",
- header: "",
+ shortDescription: "",
+ title: "",
id: 0
}
],

diff:
export default {
name: "ListForm",
props: {
- value: String
+ value: {
+ type: String,
+ required: true
+ }
}
};
</script>

diff:
export default {
name: "ListItem",
props: {
- listItem: Object
+ listItem: {
+ type: Object,
+ required: true,
+ validator: function(value) {
+ return value.text !== undefined && value._id !== undefined;
+ }
+ }
}
};
</script>

diff:
export default {
name: "MasterDetailPage",
props: {
- textSampleData: Object
+ textSampleData: {
+ type: Object,
+ required: true,
+ validator: function(value) {
+ return (
+ value.id !== undefined &&
+ value.longDescription !== undefined &&
+ value.orderDate !== undefined &&
+ value.orderTotal !== undefined &&
+ value.shipTo !== undefined &&
+ value.status !== undefined &&
+ value.title !== undefined
+ );
+ }
+ }
}
};
</script>

diff:
export default {
name: "MasterDetailSideBarTab",
props: {
- tabText: String,
- index: Number
+ tabText: {
+ type: String,
+ required: true
+ },
+ index: {
+ type: Number,
+ required: true
+ }
}
};
</script>

diff:
@@ -43,10 +43,13 @@ export default {
return {
masterDetailText: [
{
- paragraph: "",
- title: "",
- tabName: "",
- id: 0
+ id: 0,
+ longDescription: "",
+ orderDate: "",
+ orderTotal: 0,
+ shipTo: "",
+ status: "",
+ title: ""
}
],
currentDisplayTabIndex: 0,

diff:
export default {
name: "WarningMessage",
props: {
- text: String
+ text: {
+ type: String,
+ required: true
+ }
}
};
</script>

original_message:
VueJS: updated through use of props. (#1029)

better use of vue props


message:
VueJS: Cleaning up code to conform to the Vue Style Guide
styling changes to follow style guide

diff:
</div>
</div>
</main>
- <WarningMessage
+ <BaseWarningMessage
v-if="WarningMessageOpen"
- @onWarningClose="handleWarningClose"
:text="WarningMessageText"
+ @onWarningClose="handleWarningClose"
/>
</div>
</template>
<script>
import CONSTANTS from "@/constants";
import GridComponent from "@/components/GridComponent";
-import WarningMessage from "@/components/WarningMessage";
+import BaseWarningMessage from "@/components/BaseWarningMessage";
export default {
name: "VueGrid",
components: {
GridComponent,
- WarningMessage
+ BaseWarningMessage
},
data() {

diff:
<form class="input-group my-3" @submit.prevent="$emit('onAddListItem')">
<input
:value="value"
- @input="$emit('input', $event.target.value)"
- type="text"
+ aria-label="Add text here..."
class="form-control"
placeholder="Add text here..."
- aria-label="Add text here..."
+ type="text"
+ @input="$emit('input', $event.target.value)"
/>
<button type="submit" class="btn btn-primary ml-2">Submit</button>
</form>

diff:
<button
- type="button"
+ aria-label="Close"
class="close py-2"
data-dismiss="alert"
- aria-label="Close"
+ type="button"
@click="$emit('onDeleteListItem', listItem)"
>

×

diff:
Bootstrap VueList Template

- <ListForm @onAddListItem="handleAddListItem" v-model="textField" />
+ <ListForm v-model="textField" @onAddListItem="handleAddListItem" />

<ListItem
v-for="listItem in list"
:listItem="listItem"
@onDeleteListItem="handleDeleteListItem"
/>
- <WarningMessage
+ <BaseWarningMessage
v-if="WarningMessageOpen"
- @onWarningClose="handleWarningClose"
:text="WarningMessageText"
+ @onWarningClose="handleWarningClose"
/>

import CONSTANTS from "@/constants";
import ListForm from "@/components/ListForm";
import ListItem from "@/components/ListItem";
-import WarningMessage from "@/components/WarningMessage";
+import BaseWarningMessage from "@/components/BaseWarningMessage";
export default {
name: "VueList",
@@ -34,7 +34,7 @@ export default {
components: {
ListForm,
ListItem,
- WarningMessage
+ BaseWarningMessage
},
data() {

diff:
<div class="list-group list-group-flush border-bottom">
<MasterDetailSideBarTab
v-for="(textAssets, index) in masterDetailText"
- @onDisplayTabClick="handleDisplayTabClick"
- :tabText="textAssets.title"
- :index="index"
:key="textAssets.id"
+ :index="index"
+ :tabText="textAssets.title"
+ @onDisplayTabClick="handleDisplayTabClick"
/>
</div>
</div>
<MasterDetailPage :textSampleData="masterDetailText[currentDisplayTabIndex]" />
</div>
</main>
- <WarningMessage
+ <BaseWarningMessage
v-if="WarningMessageOpen"
- @onWarningClose="handleWarningClose"
:text="WarningMessageText"
+ @onWarningClose="handleWarningClose"
/>
</div>
</template>
import CONSTANTS from "@/constants";
import MasterDetailPage from "@/components/MasterDetailPage";
import MasterDetailSideBarTab from "@/components/MasterDetailSideBarTab";
-import WarningMessage from "@/components/WarningMessage";
+import BaseWarningMessage from "@/components/BaseWarningMessage";
export default {
name: "VueMasterDetail",
@@ -36,7 +36,7 @@ export default {
components: {
MasterDetailPage,
MasterDetailSideBarTab,
- WarningMessage
+ BaseWarningMessage
},
data() {

diff:
<script>
export default {
- name: "WarningMessage",
+ name: "BaseWarningMessage",
props: {
text: {
type: String,

original_message:
VueJS: Cleaning up code to conform to the Vue Style Guide (#1044)

styling changes to follow style guide


message:
VueJS: Added Bootstrap-Vue Form Validation
added bootstrap vue form validation

diff:
@@ -66,13 +66,6 @@ export default {
});
},
handleAddListItem() {
- // Warning Pop Up if the user submits an empty message
- if (!this.textField) {
- this.WarningMessageOpen = true;
- this.WarningMessageText = CONSTANTS.ERROR_MESSAGE.LIST_EMPTY_MESSAGE;
- return;
- }
-
fetch(CONSTANTS.ENDPOINT.LIST, {
method: "POST",
headers: { "Content-Type": "application/json" },

diff:
@@ -6,7 +6,7 @@ CONSTANTS.ERROR_MESSAGE = {};
CONSTANTS.ERROR_MESSAGE.LIST_DELETE = "Request to delete list item failed:";
CONSTANTS.ERROR_MESSAGE.LIST_ADD = "Request to add list item failed:";
CONSTANTS.ERROR_MESSAGE.LIST_GET = "Request to get list items failed:";
-CONSTANTS.ERROR_MESSAGE.LIST_EMPTY_MESSAGE = "Please enter a valid message";
+CONSTANTS.ERROR_MESSAGE.LIST_EMPTY_MESSAGE = "Input must be at least 1 character long.";
//}]}
CONSTANTS.ENDPOINT = {};

original_message:
VueJS: Added Bootstrap-Vue Form Validation (#1051)

added bootstrap vue form validation


message:
vue != preview
Vue out of preview

diff:
@@ -48,7 +48,7 @@ I need a full-stack web app that stores data in a Cosmos database.
Web Template Studio approaches full-stack web app creation using the following three attribute sets:
-- **Frameworks**: First, which frameworks do you want to use for your frontend and backend? We currently support two frameworks for frontend: _[React.js](https://reactjs.org/)_, _[Angular](https://angular.io/)_ and two frameworks for backend: _[Node.js](https://nodejs.org/en/)_, _[Flask](http://flask.pocoo.org/)_. We also provide a dependency checker to make sure you have the required version of Node and/or Python installed.
+- **Frameworks**: First, which frameworks do you want to use for your frontend and backend? We currently support three frameworks for frontend: _[React](https://reactjs.org/)_, _[Angular](https://angular.io/)_, _[Vue.js](https://vuejs.org/)_ and two frameworks for backend: _[Node](https://nodejs.org/en/)_, _[Flask](http://flask.pocoo.org/)_. We also provide a dependency checker to make sure you have the required version of Node and/or Python installed.
- **App pages**: Next, to accelerate app creation, we provide a number of app page templates that you can use to add common UI pages into your new app. The current page templates include: _blank page_, common layouts (*e.g., master detail) and pages that implement common patterns (*e.g., grid, list). Using the wizard, add as many of the pages as you need, providing a name for each one, and we'll generate them for you.
- **Cloud Services**: Lastly, you specify which Azure cloud services you want to use, and we'll build out the framework for the services into your app including tagging 'TODO' items. Currently supported services cover storage (_Azure Cosmos DB_), and compute (_Azure Functions_).
@@ -60,7 +60,7 @@ Please use [GitHub issues](https://github.com/Microsoft/WebTemplateStudio/issues
If you have specific feature requests or would like to vote on what others are recommending, please go to the [GitHub issues](https://github.com/Microsoft/WebTemplateStudio/issues) section as well. We would love to see what you are thinking.
-We are still extremely early in development and are looking for feedback for roadmap. Currently we are in the process of stabilizing our React & Angular with Node.js.
+We are still extremely early in development and are looking for feedback for roadmap. Currently we are in the process of stabilizing our React, Angular and Vue.js with Node and Flask.
## Contributing

diff:
@@ -44,7 +44,12 @@ Microsoft Web Template Studio is currently built and maintained by a new group o
[Nipun Jindal](https://www.linkedin.com/in/nipun-jindal-87046510a/),
[Jasmin Goh](https://www.linkedin.com/in/jasmingoh/),
[Jason Doo](https://www.linkedin.com/in/jason-doo/),
-[Tanya Tan](https://www.linkedin.com/in/tanya-tan-programmer/)
+[Tanya Tan](https://www.linkedin.com/in/tanya-tan-programmer/),
+[Ali Khamesy](https://www.linkedin.com/in/ali-khamesy-038003158/)
+
+**Program Manager Intern**
+
+[Lea Akkari](https://www.linkedin.com/in/lea-akkari-640889103/)
**Coaches**

diff:
@@ -17,7 +17,7 @@ Docs to provide useful insights.
Web Template Studio approaches full-stack web app creation using the following three attribute sets:
-- **Frameworks**: First, which frameworks do you want to use for your frontend and backend? We currently support two frameworks for frontend: _[React.js](https://reactjs.org/)_, _[Angular](https://angular.io/)_ and two frameworks for backend: _[Node.js](https://nodejs.org/en/)_, _[Flask](http://flask.pocoo.org/)_. We also provide a dependency checker to make sure you have the required version of Node and/or Python installed.
+- **Frameworks**: First, which frameworks do you want to use for your frontend and backend? We currently support three frameworks for frontend: _[React](https://reactjs.org/)_, _[Angular](https://angular.io/)_, _[Vue.js](https://vuejs.org/)_ and two frameworks for backend: _[Node](https://nodejs.org/en/)_, _[Flask](http://flask.pocoo.org/)_. We also provide a dependency checker to make sure you have the required version of Node and/or Python installed.
- **App pages**: Next, to accelerate app creation, we provide a number of app page templates that you can use to add common UI pages into your new app. The current page templates include: _blank page_, common layouts (*e.g., master detail) and pages that implement common patterns (*e.g., grid, list). Using the wizard, add as many of the pages as you need, providing a name for each one, and we'll generate them for you.
- **Cloud Services**: Lastly, you specify which Azure cloud services you want to use, and we'll build out the framework for the services into your app including tagging 'TODO' items. Currently supported services cover storage (_Azure Cosmos DB_), and compute (_Azure Functions_).
@@ -29,7 +29,7 @@ Please use [GitHub issues](https://github.com/Microsoft/WebTemplateStudio/issues
If you have specific feature requests or would like to vote on what others are recommending, please go to the [GitHub issues](https://github.com/Microsoft/WebTemplateStudio/issues) section as well. We would love to hear your thoughts.
-We are still extremely early in development and are looking for feedback for roadmap. Currently we are in the process of stabilizing our React & Angular with Node.js.
+We are still extremely early in development and are looking for feedback for roadmap. Currently we are in the process of stabilizing our React, Angular and Vue.js with Node and Flask.
## Contributing

diff:
"author": "Facebook",
"order": "1",
"licenses": "[React](https://github.com/facebook/react/blob/master/LICENSE) \n[Create React App](https://github.com/facebook/create-react-app/blob/master/LICENSE)",
- "platforms": ["Web"],
- "languages": ["Any"],
+ "platforms": [
+ "Web"
+ ],
+ "languages": [
+ "Any"
+ ],
"tags": {
"version": "16.8.4",
"preview": false
"author": "Google",
"order": "1",
"licenses": "[Angular](https://github.com/angular/angular/blob/master/LICENSE) \n[Angular CLI](https://github.com/angular/angular-cli/blob/master/LICENSE)",
- "platforms": ["Web"],
- "languages": ["Any"],
+ "platforms": [
+ "Web"
+ ],
+ "languages": [
+ "Any"
+ ],
"tags": {
"version": "8.0.2",
"preview": false
"author": "Evan You",
"order": "1",
"licenses": "[Vue](https://github.com/vuejs/vue/blob/dev/LICENSE) \n[Vue CLI](https://github.com/vuejs/vue-cli/blob/dev/LICENSE)",
- "platforms": ["Web"],
- "languages": ["Any"],
+ "platforms": [
+ "Web"
+ ],
+ "languages": [
+ "Any"
+ ],
"tags": {
"version": "2.6.6",
- "preview": true
+ "preview": false
}
}
]
\ No newline at end of file

original_message:
vue != preview (#1074)

Vue out of preview


message: Use Rails.root.join everywhere

diff:
@@ -361,8 +361,7 @@ private
def load_course_config
course = @course.name.gsub(/[^A-Za-z0-9]/, "")
begin
- load(File.join(Rails.root, "courseConfig",
- "#{course}.rb"))
+ load(Rails.root.join("courseConfig", "#{course}.rb"))
eval("extend(Course#{course.camelize})")
rescue Exception
render(text: "Error loading your course's grading " \

diff:
@@ -298,8 +298,7 @@ class AssessmentsController < ApplicationController
@submission = @assessment.submissions.find(params[:submission])
# Shows a form which has the submission on top, and feedback on bottom
begin
- subFile = File.join(Rails.root, "courses",
- @course.name, @assessment.name,
+ subFile = Rails.root.join("courses", @course.name, @assessment.name,
@assessment.handin_directory,
@submission.filename)
@submissionData = File.read(subFile)

diff:
@@ -274,7 +274,7 @@ file, most likely a duplicate email. The exact error was: #{e} "
# assessment directory.
action_auth_level :installAssessment, :instructor
def installAssessment
- @assignDir = File.join(Rails.root, "courses", @course.name)
+ @assignDir = Rails.root.join("courses", @course.name)
@availableAssessments = []
begin
Dir.foreach(@assignDir) do |filename|

diff:
@@ -30,18 +30,18 @@ class Course < ActiveRecord::Base
# generate course folder
def init_course_folder
- course_dir = File.join Rails.root, "courses", name
+ course_dir = Rails.root.join("courses", name)
FileUtils.mkdir_p course_dir
- FileUtils.touch (File.join course_dir, "autolab.log")
+ FileUtils.touch File.join(course_dir, "autolab.log")
- course_rb = File.join course_dir, "course.rb"
- default_course_rb = File.join Rails.root, "lib", "__defaultCourse.rb"
+ course_rb = File.join(course_dir, "course.rb")
+ default_course_rb = Rails.root.join("lib", "__defaultCourse.rb")
FileUtils.cp default_course_rb, course_rb
- FileUtils.mkdir_p (File.join Rails.root, "assessmentConfig")
- FileUtils.mkdir_p (File.join Rails.root, "courseConfig")
- FileUtils.mkdir_p (File.join Rails.root, "gradebooks")
+ FileUtils.mkdir_p Rails.root.join("assessmentConfig")
+ FileUtils.mkdir_p Rails.root.join("courseConfig")
+ FileUtils.mkdir_p Rails.root.join("gradebooks")
end
def order_of_dates
@@ -72,8 +72,8 @@ class Course < ActiveRecord::Base
def reload_config_file
course = name.gsub(/[^A-Za-z0-9]/, "")
- src = File.join(Rails.root, "courses", name, "course.rb")
- dest = File.join(Rails.root, "courseConfig/", "#{course}.rb")
+ src = Rails.root.join("courses", name, "course.rb")
+ dest = Rails.root.join("courseConfig/", "#{course}.rb")
s = File.open(src, "r")
lines = s.readlines
s.close
@@ -177,7 +177,7 @@ private
end
def config_file_path
- File.join(Rails.root, "courseConfig", "#{sanitized_name}.rb")
+ Rails.root.join("courseConfig", "#{sanitized_name}.rb")
end
def config_module_name

diff:
@@ -74,8 +74,7 @@ class Submission < ActiveRecord::Base
version.to_s + "_" +
assessment.handin_filename
directory = assessment.handin_directory
- path = File.join(Rails.root, "courses",
- course_user_datum.course.name,
+ path = Rails.root.join("courses", course_user_datum.course.name,
assessment.name, directory, filename)
if upload["file"]

diff:
then you can import it into your current course:

+

 Step 1: Copy the assessment from the previous course directory to the current course directory (<%= File.join("courses", @course.name) %>).

Step 2: Refresh this page to cause your new assessment to appear

diff:
@@ -13,9 +13,9 @@ namespace :autolab do
AUTOGRADE_CATEGORY_NAME = "CategoryAutograde"
AUTOGRADE_TEMPLATE_DIR_PATH =
- File.join(Rails.root, "templates", "labtemplate")
+ Rails.root.join("templates", "labtemplate")
AUTOGRADE_TEMPLATE_CONFIG_PATH =
- File.join(Rails.root, "templates", "AutoPopulated-labtemplate.rb")
+ Rails.root.join("templates", "AutoPopulated-labtemplate.rb")
AUTOGRADE_TEMPLATE_NAME = "labtemplate"
AUTOGRADE_TEMPLATE_DISPLAY_NAME = "Lab Template"
AUTOGRADE_TEMPLATE_MAX_SCORE = 100.0
@@ -38,7 +38,7 @@ namespace :autolab do
end
def load_assessments course
- course_dir = File.join(Rails.root, "courses", course.name)
+ course_dir = Rails.root.join("courses", course.name)
ASSESSMENT_CATEGORIES.each do |cat|
# start date for this category
@@ -173,7 +173,7 @@ namespace :autolab do
end
def load_submissions_for(course, cud)
- course_dir = File.join(Rails.root, "courses", course.name)
+ course_dir = Rails.root.join("courses", course.name)
user = cud.user
course.assessments.each do |a|
@@ -228,12 +228,12 @@ namespace :autolab do
end
def add_assessment_files course
- course_dir = File.join Rails.root, "courses", course.name
+ course_dir = Rails.root.join("courses", course.name)
course.assessments.each do |a|
assessment_dir = File.join(course_dir, a.name)
assessment_handin_dir = File.join(assessment_dir, a.handin_directory)
- assessment_template_path = File.join(Rails.root, "lib", "__defaultAssessment.rb")
+ assessment_template_path = Rails.root.join("lib", "__defaultAssessment.rb")
assessment_template = nil
File.open(assessment_template_path) do |f|
@@ -262,7 +262,7 @@ namespace :autolab do
def load_autograde_assessment course
- course_dir = File.join(Rails.root, "courses", course.name)
+ course_dir = Rails.root.join("courses", course.name)
# Create assessment
asmt = course.assessments.create! do |a|
@@ -299,7 +299,7 @@ namespace :autolab do
FileUtils.cp_r(AUTOGRADE_TEMPLATE_DIR_PATH, course_dir)
# Copy assessment config
- assessmentConfig_dir = File.join(Rails.root, "assessmentConfig")
+ assessmentConfig_dir = Rails.root.join("assessmentConfig")
FileUtils.cp(AUTOGRADE_TEMPLATE_CONFIG_PATH, assessmentConfig_dir)
# Reload config file
@@ -361,8 +361,8 @@ namespace :autolab do
def delete_course course
if course
- course_dir = File.join(Rails.root, "courses", course.name)
- course_config_dir = File.join(Rails.root, "courseConfig")
+ course_dir = Rails.root.join("courses", course.name)
+ course_config_dir = Rails.root.join("courseConfig")
course_config_path = File.join(course_config_dir, "#{course.name}.rb")
course.destroy

license: Apache License 2.0

repo: autolab/autolab

original_message: Use Rails.root.join everywhere (#752)


message: Adapt to new roster format

diff:
@@ -27,7 +27,8 @@ class CoursesController < ApplicationController
redirect_to course_assessments_url(@course)
end
- NEW_ROSTER_COLUMNS = 29
+ ROSTER_COLUMNS_S15 = 29
+ ROSTER_COLUMNS_F16 = 32
action_auth_level :manage, :instructor
def manage
@@ -577,15 +578,32 @@ private
parsedRoster = CSV.parse(roster)
if parsedRoster[0][0].nil?
fail "Roster cannot be recognized"
- elsif (parsedRoster[0].length == NEW_ROSTER_COLUMNS)
+ elsif (parsedRoster[0].length == ROSTER_COLUMNS_F16)
+ # In CMU S3 roster. Columns are:
+ # Semester(0), Course(1), Section(2), (Lecture-skip)(3), (Mini-skip)(4),
+ # Last Name(5), First Name(6), (MI-skip)(7), Andrew ID(8),
+ # (Email-skip)(9), School(10), (Department-skip)(11), Major(12),
+ # Year(13), (skip)(14), Grade Policy(15), ...
+ map=[0, 8, 5, 6, 10, 12, 13, 15, -1, 1, 2]
+ select_columns=ROSTER_COLUMNS_F16
+ elsif (parsedRoster[0].length == ROSTER_COLUMNS_S15)
# In CMU S3 roster. Columns are:
# Semester(0), Lecture(1), Section(2), (skip)(3), (skip)(4), Last Name(5),
# First Name(6), (skip)(7), Andrew ID(8), (skip)(9), School(10),
# Major(11), Year(12), (skip)(13), Grade Policy(14), ...
+ map=[0, 8, 5, 6, 10, 11, 12, 14, -1, 1, 2]
+ select_columns=ROSTER_COLUMNS_S15
+ else
+ # No header row. Columns are:
+ # Semester(0), Email(1), Last Name(2), First Name(3), School(4),
+ # Major(5), Year(6), Grade Policy(7), (skip)(8), Lecture(9),
+ # Section(10), ...
+ return parsedRoster
+ end
# Sanitize roster input, ignoring empty / incomplete lines.
# Also requires each line to have an andrewID, else ignores it
- parsedRoster.select! { |row| row.length == NEW_ROSTER_COLUMNS && row[8] != nil}
+ parsedRoster.select! { |row| row.length == select_columns && row[map[1]] != nil}
# Detect if there is a header row
if (parsedRoster[0][0] == "Semester")
offset = 1
@@ -595,31 +613,24 @@ private
numRows = parsedRoster.length - offset
convertedRoster = Array.new(numRows) { Array.new(11) }
- for i in 0..(numRows - 1)
- convertedRoster[i][0] = parsedRoster[i + offset][0]
if (Rails.env == "production")
- convertedRoster[i][1] = parsedRoster[i + offset][8] + "@andrew.cmu.edu"
+ domain="andrew.cmu.edu"
else
- convertedRoster[i][1] = parsedRoster[i + offset][8] + "@foo.bar"
+ domain="foo.bar"
end
- convertedRoster[i][2] = parsedRoster[i + offset][5]
- convertedRoster[i][3] = parsedRoster[i + offset][6]
- convertedRoster[i][4] = parsedRoster[i + offset][10]
- convertedRoster[i][5] = parsedRoster[i + offset][11]
- convertedRoster[i][6] = parsedRoster[i + offset][12]
- convertedRoster[i][7] = parsedRoster[i + offset][14]
- convertedRoster[i][9] = parsedRoster[i + offset][1]
- convertedRoster[i][10] = parsedRoster[i + offset][2]
- end
- return convertedRoster
+ for i in 0..(numRows - 1)
+ for j in 0..10
+ if map[j] >= 0
+ if j == 1
+ convertedRoster[i][j] = parsedRoster[i + offset][map[j]] + "@" + domain
else
- # No header row. Columns are:
- # Semester(0), Email(1), Last Name(2), First Name(3), School(4),
- # Major(5), Year(6), Grade Policy(7), (skip)(8), Lecture(9),
- # Section(10), ...
- return parsedRoster
+ convertedRoster[i][j] = parsedRoster[i + offset][map[j]]
+ end
+ end
end
end
+ return convertedRoster
+ end
def extract_asmt_for_moss(tmp_dir, assessments)
# for each assessment

original_message: Adapt to new roster format (#726)


message: Non exist course 404 error

diff:
@@ -141,8 +141,7 @@ protected
@course = Course.find_by(name: course_name) if course_name
unless @course
- flash[:error] = "Course #{params[:course_name]} does not exist!"
- redirect_to(controller: :home, action: :error) && return
+ render :file => "#{Rails.root}/public/404.html", :status => 404
end
# set course logger

original_message: Non exist course 404 error (#768)


diff:
@@ -36,7 +36,7 @@ class AttachmentsController < ApplicationController
flash[:error] = "Could not find Attachment # #{params[:id]}"
redirect_to([@course, :attachments]) && return
end
- filename = File.join("attachments", @attachment.filename)
+ filename = Rails.root.join("attachments", @attachment.filename)
unless File.exist?(filename)
flash[:error] = "Error loading #{@attachment.name} from #{@attachment.filename}"
redirect_to([@course, :attachments]) && return

original_message: Attachments#show should use an absolute path (#751)


diff:
@@ -240,6 +240,7 @@ class SubmissionsController < ApplicationController
else
mime = params[:forceMime] || @submission.detected_mime_type
+
send_file @filename,
filename: @basename,
disposition: "inline"
@@ -383,6 +384,11 @@ private
@filename = @submission.handin_file_path
@basename = File.basename @filename
+ basename_parts = @basename.split("_")
+ basename_parts.insert(-3, @assessment.name)
+
+ @basename = basename_parts.join("_")
+
unless File.exist? @filename
flash[:error] = "Could not find submission file."
redirect_to [@course, @assessment] and return false

original_message: Rename files to include assignment name on download. Fixes #693. (#772)


diff:
Summary

<% if @problemSummaries.empty? then %>
<% if (@cud.instructor? or @cud.course_assistant?) then %>
-
Click anywhere on the code to annotate.

+
Hover over a line of code and click the green box to annotate.

<% else %>

Nothing to summarize yet.

<% end %>

original_message: Updated message to properly inform how to annotate a submission. (#773)


diff:
<%= f.text_field :max_size, help_text: "The maximum size that a handin file can have, in megabytes (MB)." %>
<div class="action_buttons">
- <%= submit_tag "Save" , {:class=>"waves-effect waves-light btn primary", :name=>"handin", } %>
+ <%= f.submit "Save", :name=>"handin" %>
</div>

diff:
<%= f.score_adjustment_input :version_penalty, optional: true, help_text: "The penalty applied to submissions with version greater than the version threshold. It represents the number of points or the percentage of the total score removed per version above the threshold, and must be a non-negative number. For example, if this is set to 1 point and the version threshold to 3, the fifth version of a student's submissions would be docked 2 points. If left blank, the course default is used." %>
<div class="action_buttons">
- <%= submit_tag "Save" , {:class=>"waves-effect waves-light btn primary", :name=>"penalties", } %>
+ <%= f.submit "Save", :name=>"penalties" %>
</div>

original_message: Fix submit buttons on penalties and handin page. (#829)


diff:
</script>
<% end %>
+<% content_for :stylesheets do %>
+ <style>
+ .collection-item {
+ overflow: auto;
+ }
+ </style>
+<% end %>
+
<%= render partial: "announcements/announcements_list",
locals: { announcements: @announcements } %>

original_message: Add overflow to large title assignments so the badge within the link functions properly. (#848)


message: Fix issue by stopping propogation of click events in the buttons

diff:
@@ -92,6 +92,11 @@ jQuery(function($) {
updateBatchRegradeButton();
}
+ $("#submissions").on("click", ".exclude-click i", function (e) {
+ e.stopPropagation();
+ return;
+ });
+
$('#submissions').on("click", ".submission-row", function(e) {
// Don't toggle row if we originally clicked on an anchor tag
if(e.target.localName != 'a') {

original_message: Fix issue #844 by stopping propogation of click events in the buttons (#845)


message:
regradeAll: refer to correct submissions variable
Fix misuse of in regradeAll and regradeBatch

diff:
@@ -76,7 +76,7 @@ module AssessmentAutograde
redirect_to([@course, @assessment, :submissions]) && return
elsif job < 0 # autograding failed
failed_jobs += 1
- failed_list += "#{@submission.filename}: autograding error.
"
+ failed_list += "#{submission.filename}: autograding error.
"
end
else
failed_jobs += 1
@@ -119,7 +119,7 @@ module AssessmentAutograde
redirect_to([@course, @assessment, :submissions]) && return
elsif job < 0 # autograding failed
failed_jobs += 1
- failed_list += "#{@submission.filename}: autograding error.
"
+ failed_list += "#{submission.filename}: autograding error.
"
end
else
failed_jobs += 1

original_message:
regradeAll: refer to correct submissions variable

Fix misuse of @submission in regradeAll and regradeBatch


diff:
<script>
// Not using Jquery to bind the listener since it's not loaded at this point
document.addEventListener("DOMContentLoaded", function(event) {
- $('.button-collapse').sideNav();
+ $('.button-collapse').sideNav({
+ edge: 'right'
+ });
});
</script>

original_message: Move the nav drawer to the right side popout. (#857)


message: Due date fix

diff:
@@ -535,9 +535,16 @@ class AssessmentsController < ApplicationController
@assessment.save!
end
+
+ begin
flash[:success] = "Saved!" if @assessment.update!(edit_assessment_params)
redirect_to(tab_index) && return
+ rescue ActiveRecord::RecordInvalid => invalid
+ flash[:error] = invalid.message.sub! "Validation failed: ", ""
+
+ redirect_to(tab_index) && return
+ end
end
action_auth_level :releaseAllGrades, :instructor

diff:
@@ -435,8 +435,9 @@ private
end
def verify_dates_order
- errors.add :start_at, "must be before time assessment is due at" if start_at > due_at
- errors.add :due_at, "must be before time assessment ends at" if due_at > end_at
+ errors.add :due_at, "must be after the start date" if start_at > due_at
+ errors.add :end_at, "must be after the due date" if due_at > end_at
+ errors.add :grading_deadline, "must be after the end date" if end_at > grading_deadline
end
def handin_directory_and_filename_or_disable_handins

original_message: Due date fix (#871)


message:
Log more autograding errors
Integrating a few of cg2v's improvements:
* Log more autograding errors
Try to always send something to the course log when autograding
submission to tango fails or is incomplete
* Fix logging (flash is a string, not array)

diff:
@@ -175,6 +175,10 @@ module AssessmentAutograde
flash[:success] = ("Submitted file #{submissions[0].filename} (#{link}) for autograding." \
" Refresh the page to see the results.").html_safe
end
+ if job < 0
+ COURSE_LOGGER.log("SendJob failed for #{submissions[0].id}: code #{job}")
+ COURSE_LOGGER.log("SendJob user error message #{flash[:error]}")
+ end
job
end
@@ -220,6 +224,7 @@ module AssessmentAutograde
File.open(f["localFile"], "rb").read)
rescue TangoClient::TangoException => e
flash[:error] = "Error while uploading autograding files: #{e.message}"
+ COURSE_LOGGER.log("Error while uploading autograding files for #{submission.id}: #{e.message}")
return -4, nil
end
end
@@ -235,16 +240,21 @@ module AssessmentAutograde
# Generate the dave number/string, this is used when autograding is done.
# The key is not guaranteed to be unique, but it's gonna be unique.
dave = (0...60).map { 65.+(rand(25)).chr }.join
-
- begin
+ failed = false
# save dave keys. These let us know which submissions to save the autoresult for
ActiveRecord::Base.transaction do
submissions.each do |submission|
submission.dave = dave
- submission.save!
+ if not submission.save
+ COURSE_LOGGER.log("Error while updating submission #{submission.id} callback key:")
+ submission.errors.full_messages.each do |msg|
+ COURSE_LOGGER.log(" (#{submission.id}): #{msg}")
end
+ failed = true
end
- rescue
+ end
+ end
+ if failed
return -13, nil
end
@@ -302,6 +312,7 @@ module AssessmentAutograde
response = TangoClient.addjob("#{course.name}-#{assessment.name}", job_properties)
rescue TangoClient::TangoException => e
flash[:error] = "Error while adding job to the queue: #{e.message}"
+ COURSE_LOGGER.log("Error while adding job to the queue: #{e.message}")
return -9, nil
end
[0, response]
@@ -326,14 +337,17 @@ module AssessmentAutograde
end
end
rescue Timeout::Error
+ COURSE_LOGGER.log("Error while polling for #{submissions[0].id} job status: Timeout")
return -11
rescue TangoClient::TangoException => e
flash[:error] = "Error while polling for job status: #{e.message}"
+ COURSE_LOGGER.log("Error while polling for #{submissions[0].id} job status: #{e.message}")
return -11
end
if feedback.nil?
return -12
+ COURSE_LOGGER.log("Error while polling for #{submissions[0].id} job status: No feedback")
else
if assessment.overwrites_method?(:autogradeDone)
assessment.config_module.autogradeDone(submissions, feedback)
@@ -366,7 +380,7 @@ module AssessmentAutograde
existing_files = TangoClient.open("#{course.name}-#{assessment.name}")
rescue TangoClient::TangoException => e
flash[:error] = "Error with open request on Tango: #{e.message}"
- COURSE_LOGGER.log("#{e.message}")
+ COURSE_LOGGER.log("Error with open request on Tango for submission #{submissions[0].id}: #{e.message}")
return -1
end
@@ -588,7 +602,7 @@ module AssessmentAutograde
rescue StandardError => error
COURSE_LOGGER.log(error)
- COURSE_LOGGER.log(error.backtrace)
+ error.backtrace.each { |line| COURSE_LOGGER.log(line) }
raise error
end
end

original_message:
Log more autograding errors (#709)

Integrating a few of cg2v's improvements:

* Log more autograding errors

Try to always send something to the course log when autograding
submission to tango fails or is incomplete

* Fix logging (flash is a string, not array)


message: add debounce code to prevent the client from making multiple submissions after clicking the submit button

diff:
<% end %>
<% content_for :javascripts do %>
<script>
+ var submittedFile = false;
+
+ function flashError(message) {
+ const elem = $("<div />").prop("id", "flash_error").addClass("error").text(message);
+ $("#flashes").append(
+ elem
+ );
+
+ setTimeout(() => {
+ elem.hide(500);
+ }, 3000);
+ }
+
function dropHandler(e) {
e.preventDefault();
$(".drag-drop-handin").get(0).style = undefined;
- handleFiles(e.dataTransfer.files);
+ if (e.dataTransfer.files.length === 1) {
+ var fileSelector = $("#handin_show_assessment input[type='file']").get(0);
+ fileSelector.files = e.dataTransfer.files;
+ if (fileSelector.files.length === 1) {
+ showFiles();
+ enableSubmit();
+ }
+ }
}
function dragOverHandler(e) {
false
);
- function handleFiles(fileList) {
- if (fileList.length === 1) {
- var fileSelector = $("#handin_show_assessment input[type='file']").get(0);
- fileSelector.files = fileList;
- showFiles();
- } else {
- // invalid number of files alert
- }
- }
-
function showFiles() {
var fileSelector = $("#handin_show_assessment input[type='file']").get(0);
var file = fileSelector.files[0];
- console.log(file);
$("#handin-file-name").text(file.name);
$("#handin-modify-date").text(moment(file.lastModified).format("MMMM Do YYYY, h:mm a"));
+ submittedFile = false;
var sOutput = file.size + " bytes";
for (var aMultiples = ["kb", "mb", "gb", "tb", "pb", "eb", "zb", "yb"], nMultiple = 0, nApprox = file.size / 1024; nApprox > 1; nApprox /= 1024, nMultiple++) {
}
}
+ if (fileSelector.files.length === 1) {
$(".handin-row").hide();
$(".handedin-row").show();
enableSubmit();
}
+ }
$("#integrity_checkbox").change(function (e) {
enableSubmit();
e.preventDefault();
var fileSelector = $("#handin_show_assessment input[type='file']").get(0);
fileSelector.value = null;
- $(".handin-row").show();
- $(".handedin-row").hide();
+ $(".handin-row").show(function () {
enableSubmit();
});
+ $(".handedin-row").hide();
+ });
function enableSubmit() {
var checkbox = document.getElementById("integrity_checkbox");
} else {
$("#fake-submit").removeClass("disabled");
}
+
+ if (checkbox.checked && $(".handin-row").is(":hidden") && $("#fake-submit").hasClass("disabled") && fileSelector.files.length === 0) {
+ // theres an issue
+ $(".handin-row").show();
+ $(".handedin-row").hide();
+ flashError("There was an error processing your file, please try again.");
+ }
}
+
+ $("#fake-submit").click(function (e) {
+ if (submittedFile) {
+ e.preventDefault();
+ return;
+ }
+ $("#fake-submit").addClass("disabled");
+ submittedFile = true;
+ });
</script>
<% end %>
<div class="row handin-row">


message: fix a bug where sometimes on chrome, a user that clicks to upload a file has to select the file twice.

diff:
document.querySelector("#handin_show_assessment input[type='file']").addEventListener(
"change",
function (e) {
+ console.log("files changed");
showFiles();
},
false
<% end %>

-
Drag a file here to hand in. Click to select a file.
 Files do not submit automatically.

+
Drag a file here to hand in. Click to select a file.
 Files do not submit automatically.


message: Add error flash for strange bug that can occur in firefox where dragged files may not end up 'sticking' in the file upload input, prompting user to reupload their files instead of failing silently'

diff:
var submittedFile = false;
function flashError(message) {
- const elem = $("
").prop("id", "flash_error").addClass("error").text(message);
- $("#flashes").append(
- elem
- );
-
- setTimeout(() => {
- elem.hide(500);
- }, 3000);
+ const elem = $(".file-error").text(message).show();
}
function dropHandler(e) {
document.querySelector("#handin_show_assessment input[type='file']").addEventListener(
"change",
function (e) {
- console.log("files changed");
showFiles();
},
false
}
if (fileSelector.files.length === 1) {
- $(".handin-row").hide();
+ $(".handin-row").hide(0, function () {
+ $(".file-error").hide();
+ });
$(".handedin-row").show();
enableSubmit();
}
</script>
<% end %>

+

+ There was an error processing your file, please try adding it again.
+

Drag a file here to hand in. Click to select a file.
 Files do not submit automatically.


message:
Really only display students' own jobs
When deciding what jobs can be displayed to students, check the
whole email address part in the job name instead of treating
the email address as a suffix. Otherwise, students whose userid
is a right-anchored substring of another students userid will
see the latter student's jobs

diff:
@@ -213,7 +213,7 @@ protected
unless @cud.user.administrator?
if !@cud.instructor?
# Students can see only their own job names
- job[:name] = "*" unless job[:name].ends_with? @cud.user.email
+ job[:name] = "*" unless job[:name].ends_with? "_#{@cud.user.email}"
else
# Instructors can see only their course's job names
job[:name] = "*" if !rjob["notifyURL"] || !(job[:course].eql? @cud.course.id.to_s)

original_message:
Really only display students' own jobs (#1003)

When deciding what jobs can be displayed to students, check the
whole email address part in the job name instead of treating
the email address as a suffix. Otherwise, students whose userid
is a right-anchored substring of another students userid will
see the latter student's jobs


diff:
@@ -35,8 +35,7 @@ jQuery(function($) {
'sPaginationType': 'full_numbers',
'iDisplayLength': 100,
'oLanguage': {
- 'sLengthMenu':'
- <input type="checkbox" id="only-latest">' +
+ 'sLengthMenu':'<input type="checkbox" id="only-latest">' +
'Show only latest'
},
"columnDefs": [{

original_message: Remove errant newline from manage_submissions.js (#1141)


message:
Don't chdir in moss extraction
moss extraction uses absolute paths, so there's no need to
change directories, and several things break when the app's working
directory is not Rails.root

diff:
@@ -736,7 +736,6 @@ private
begin
Dir.mkdir(extTarDir)
Dir.mkdir(baseFilesDir) # To hold all basefiles
- Dir.chdir(baseFilesDir)
rescue
end
@@ -750,7 +749,6 @@ private
begin
Dir.mkdir(extFilesDir) # To hold all submissions
- Dir.chdir(extFilesDir)
rescue
end

original_message:
Don't chdir in moss extraction (#1163)

moss extraction uses absolute paths, so there's no need to
change directories, and several things break when the app's working
directory is not Rails.root


message:
Update Carnegie Mellon LDAP integration
Stop using the legacy LDAP server
Check for the currently used value denoting first year students

diff:
@@ -183,10 +183,9 @@ class User < ApplicationRecord
require "rubygems"
require "net/ldap"
- host = "ldap.andrew.cmu.edu"
+ host = "ldap.cmu.edu"
ldap = Net::LDAP.new(host: host, port: 389)
- user = ldap.search(base: "ou=Person,dc=cmu,dc=edu",
- filter: "cmuAndrewId=" + andrewID)[0]
+ user = ldap.search(base: "uid=" + andrewID + ",ou=AndrewPerson,dc=andrew,dc=cmu,dc=edu")[0]
return unless user
@@ -226,6 +225,8 @@ class User < ApplicationRecord
result[:year] = case user[:cmustudentclass][0]
when "Freshman" then "1"
+ when "First-Year student" then "1"
+ when "First-Year Student" then "1"
when "Sophomore" then "2"
when "Junior" then "3"
when "Senior" then "4"

original_message:
Update Carnegie Mellon LDAP integration (#1422)

- Stop using the legacy LDAP server
- Check for the currently used value denoting first year students


message:
Generate github callback url with https
Copy the logic from AssessmentAutogradeCore:get_callback_url to generate
the callback url for github. That will cause https vs http to be used
appropriately.

diff:
@@ -206,9 +206,25 @@ class UsersController < ApplicationController
else
github_integration.update!(oauth_state: state)
end
+ # Use https, unless explicitly specified not to
+ prefix = "https://"
+ if ENV["DOCKER_SSL"] == "false"
+ prefix = "http://"
+ end
+
+ begin
+ if Rails.env.development?
+ hostname = request.base_url
+ else
+ hostname = prefix + request.host
+ end
+ rescue
+ hostname = `hostname`
+ hostname = prefix + hostname.strip
+ end
authorize_url_params = {
- redirect_uri: "http://#{request.host}:#{request.port}/users/github_oauth_callback",
+ redirect_uri: "#{hostname}/users/github_oauth_callback",
scope: "repo",
state: state
}

original_message:
Generate github callback url with https (#1434)

Copy the logic from AssessmentAutogradeCore:get_callback_url to generate
the callback url for github. That will cause https vs http to be used
appropriately.


message: add privilege check of some functions in ghttp and fix some small bugs in gbuild and makefile

diff:
@@ -41,7 +41,7 @@ main(int argc, char * argv[])
int len = _db_path.length();
if(_db_path.length() > 3 && _db_path.substr(len-3, 3) == ".db")
{
- cout<<"your database can not end with .db"<<endl;
+ cout<<"your database can not end with .db or less than 3 characters"<<endl;
return -1;
}

diff:
@@ -191,6 +191,7 @@ struct User{
std::set<std::string> unload_priv;
std::set<std::string> backup_priv;
std::set<std::string> restore_priv;
+ std::set<std::string> export_priv;
pthread_rwlock_t query_priv_set_lock;
pthread_rwlock_t update_priv_set_lock;
@@ -198,7 +199,7 @@ struct User{
pthread_rwlock_t unload_priv_set_lock;
pthread_rwlock_t backup_priv_set_lock;
pthread_rwlock_t restore_priv_set_lock;
-
+ pthread_rwlock_t export_priv_set_lock;
/*
Database *build_priv[MAX_DATABASE_NUM];
Database *load_priv[MAX_DATABASE_NUM];
@@ -212,6 +213,7 @@ struct User{
pthread_rwlock_init(&unload_priv_set_lock, NULL);
pthread_rwlock_init(&backup_priv_set_lock, NULL);
pthread_rwlock_init(&restore_priv_set_lock, NULL);
+ pthread_rwlock_init(&export_priv_set_lock, NULL);
}
User(string _username, string _password){
if(_username == "")
@@ -229,6 +231,7 @@ struct User{
pthread_rwlock_init(&unload_priv_set_lock, NULL);
pthread_rwlock_init(&backup_priv_set_lock, NULL);
pthread_rwlock_init(&restore_priv_set_lock, NULL);
+ pthread_rwlock_init(&export_priv_set_lock, NULL);
}
~User(){
pthread_rwlock_destroy(&query_priv_set_lock);
@@ -237,6 +240,7 @@ struct User{
pthread_rwlock_destroy(&unload_priv_set_lock);
pthread_rwlock_destroy(&backup_priv_set_lock);
pthread_rwlock_destroy(&restore_priv_set_lock);
+ pthread_rwlock_destroy(&export_priv_set_lock);
}
std::string getPassword(){
return password;
@@ -336,6 +340,21 @@ struct User{
}
return backup_db;
}
+ std::string getexport(){
+ std::string export_db;
+ if(username == ROOT_USERNAME)
+ {
+ export_db = "all";
+ return export_db;
+ }
+ std::set<std::string>::iterator it = export_priv.begin();
+ while(it != export_priv.end())
+ {
+ export_db = export_db + *it + " ";
+ ++it;
+ }
+ return export_db;
+ }
void setPassword(string psw)
{
password = psw;
@@ -1782,7 +1801,7 @@ void build_thread(const shared_ptr<HttpServer::Response>& response, const shared
f.close();
//by default, one can query or load or unload the database that is built by itself, so add the database name to the privilege set of the user
- if(addPrivilege(username, "query", db_name) == 0 || addPrivilege(username, "load", db_name) == 0 || addPrivilege(username, "unload", db_name) == 0 || addPrivilege(username, "backup", db_name) == 0 || addPrivilege(username, "restore", db_name) == 0)
+ if(addPrivilege(username, "query", db_name) == 0 || addPrivilege(username, "load", db_name) == 0 || addPrivilege(username, "unload", db_name) == 0 || addPrivilege(username, "backup", db_name) == 0 || addPrivilege(username, "restore", db_name) == 0 || addPrivilege(username, "export", db_name) == 0)
{
string error = "add query or load or unload privilege failed.";
string resJson = CreateJson(912, error, 0);
@@ -2259,6 +2278,19 @@ void drop_thread(const shared_ptr<HttpServer::Response>& response, const shared_
cout << "check identity successfully." << endl;
+ if(checkPrivilege(username, "drop", db_name) == 0)
+ {
+ string error = "no load privilege, operation failed.";
+ string resJson = CreateJson(302, error, 0);
+ *response << "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " << resJson.length() << "\r\n\r\n" << resJson;
+
+
+ //*response << "HTTP/1.1 200 OK\r\nContent-Length: " << error.length() << "\r\n\r\n" << error;
+ //return false;
+ return;
+ }
+ cout << "check privilege successfully." << endl;
+
//check if the db_name is system
if (db_name == "system")
{
@@ -2452,6 +2484,19 @@ void export_thread(const shared_ptr<HttpServer::Response>& response, const share
pthread_rwlock_unlock(&users_map_lock);
cout << "check identity successfully." << endl;
+ if(checkPrivilege(username, "export", db_name) == 0)
+ {
+ string error = "no load privilege, operation failed.";
+ string resJson = CreateJson(302, error, 0);
+ *response << "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " << resJson.length() << "\r\n\r\n" << resJson;
+
+
+ //*response << "HTTP/1.1 200 OK\r\nContent-Length: " << error.length() << "\r\n\r\n" << error;
+ //return false;
+ return;
+ }
+ cout << "check privilege successfully." << endl;
+
//check if the db_name is system
if (db_name == "system")
{
@@ -3921,8 +3966,7 @@ void user_thread(const shared_ptr<HttpServer::Response>& response, const shared_
pthread_rwlock_unlock(&users_map_lock);
}
- else if(type == "add_query" || type == "add_load" || type == "add_unload" || type == "add_update" || type == "add_backup" || type ==
- "add_restore")
+ else if(type == "add_query" || type == "add_load" || type == "add_unload" || type == "add_update" || type == "add_backup" || type == "add_restore" || "add_export")
{
if(username2 == ROOT_USERNAME)
{
@@ -3949,7 +3993,7 @@ void user_thread(const shared_ptr<HttpServer::Response>& response, const shared_
return;
}
}
- else if(type == "delete_query" || type == "delete_load" || type == "delete_unload" || type == "delete_update" || type == "delete_backup" || type == "delete_restore")
+ else if(type == "delete_query" || type == "delete_load" || type == "delete_unload" || type == "delete_update" || type == "delete_backup" || type == "delete_restore" || type == "delete_export" )
{
if(username2 == ROOT_USERNAME)
{
@@ -4493,6 +4537,15 @@ bool addPrivilege(string username, string type, string db_name)
pthread_rwlock_unlock(&(it->second->backup_priv_set_lock));
}
+ else if(type == "export")
+ {
+ pthread_rwlock_wrlock(&(it->second->export_priv_set_lock));
+ it->second->export_priv.insert(db_name);
+ string update = "INSERT DATA {<" + username + "> <has _export_priv> <" + db_name + ">.}";
+ updateSys(update);
+
+ pthread_rwlock_unlock(&(it->second->export_priv_set_lock));
+ }
return 1;
}
else
@@ -4569,7 +4622,16 @@ bool delPrivilege(string username, string type, string db_name)
pthread_rwlock_unlock(&(it->second->restore_priv_set_lock));
return 1;
}
+ else if(type == "export" && it->second->export_priv.find(db_name) != it->second->export_priv.end())
+ {
+ pthread_rwlock_wrlock(&(it->second->export_priv_set_lock));
+ it->second->export_priv.erase(db_name);
+ string update = "DELETE DATA {<" + username + "> <has_export_priv> <" + db_name + ">.}";
+ updateSys(update);
+ pthread_rwlock_unlock(&(it->second->export_priv_set_lock));
+ return 1;
+ }
}
pthread_rwlock_unlock(&users_map_lock);
return 0;
@@ -4650,6 +4712,17 @@ bool checkPrivilege(string username, string type, string db_name)
}
pthread_rwlock_unlock(&(it->second->backup_priv_set_lock));
}
+ else if(type == "export")
+ {
+ pthread_rwlock_rdlock(&(it->second->export_priv_set_lock));
+ if(it->second->export_priv.find(db_name) != it->second->export_priv.end())
+ {
+ pthread_rwlock_unlock(&(it->second->export_priv_set_lock));
+ pthread_rwlock_unlock(&users_map_lock);
+ return 1;
+ }
+ pthread_rwlock_unlock(&(it->second->export_priv_set_lock));
+ }
pthread_rwlock_unlock(&users_map_lock);
return 0;
}
@@ -5001,6 +5074,19 @@ void backup_thread(const shared_ptr<HttpServer::Response>& response, const share
cout << "check identity successfully." << endl;
+ if(checkPrivilege(username, "backup", db_name) == 0)
+ {
+ string error = "no unload privilege, operation failed.";
+ string resJson = CreateJson(601, error, 0);
+ *response << "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " << resJson.length() << "\r\n\r\n" << resJson;
+
+
+ //*response << "HTTP/1.1 200 OK\r\nContent-Length: " << error.length() << "\r\n\r\n" << error;
+ //return false;
+ return;
+ }
+ cout << "check privilege successfully." << endl;
+
//check if the db_name is system
if (db_name == "system")
{
@@ -5045,22 +5131,6 @@ void backup_thread(const shared_ptr<HttpServer::Response>& response, const share
}
pthread_rwlock_unlock(&already_build_map_lock);
- //check if database named [db_name] is already load
- /*
- //check privilege
- if(checkPrivilege(username, "restore", db_name) == 0)
- {
- string error = "no restore privilege, operation failed.";
- string resJson = CreateJson(302, error, 0);
- *response << "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " << resJson.length() << "\r\n\r\n" << resJson;
-
-
- //*response << "HTTP/1.1 200 OK\r\nContent-Length: " << error.length() << "\r\n\r\n" << error;
- //return false;
- return;
- }
- cout << "check privilege successfully." << endl;
- */
cout << database << endl;
@@ -5187,6 +5257,19 @@ void restore_thread(const shared_ptr<HttpServer::Response>& response, const shar
cout << "check identity successfully." << endl;
+ if(checkPrivilege(username, "restore", db_name) == 0)
+ {
+ string error = "no unload privilege, operation failed.";
+ string resJson = CreateJson(601, error, 0);
+ *response << "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " << resJson.length() << "\r\n\r\n" << resJson;
+
+
+ //*response << "HTTP/1.1 200 OK\r\nContent-Length: " << error.length() << "\r\n\r\n" << error;
+ //return false;
+ return;
+ }
+ cout << "check privilege successfully." << endl;
+
//check if the db_name is system
if (db_name == "system")
{
@@ -5226,22 +5309,6 @@ void restore_thread(const shared_ptr<HttpServer::Response>& response, const shar
return;
}
-
- /*
- //check privilege
- if(checkPrivilege(username, "restore", db_name) == 0)
- {
- string error = "no restore privilege, operation failed.";
- string resJson = CreateJson(302, error, 0);
- *response << "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: " << resJson.length() << "\r\n\r\n" << resJson;
-
-
- //*response << "HTTP/1.1 200 OK\r\nContent-Length: " << error.length() << "\r\n\r\n" << error;
- //return false;
- return;
- }
- cout << "check privilege successfully." << endl;
- */
cout << database << endl;
pthread_rwlock_rdlock(&already_build_map_lock);

old_path: makefile

new_path: makefile

diff:
@@ -576,7 +576,6 @@ clean:
#rm -rf .project .cproject .settings just for eclipse
rm -rf logs/*.log
rm -rf *.out # gmon.out for gprof with -pg
- cd tools/antlr4.7.2/antlr4-cpp-runtime-4/; make clean
dist: clean

license: BSD 3-Clause New or Revised License

repo: pkumod/gstore


message: fix bug of backup when create multilevel path

diff:
@@ -5003,7 +5003,7 @@ int copy(string src_path, string dest_path)
}
if(!boost::filesystem::exists(dest_path)){
//check the destnation path
- sys_cmd = "mkdir ./" + dest_path;
+ sys_cmd = "mkdir -p ./" + dest_path;
system(sys_cmd.c_str());
}
@@ -5146,8 +5146,6 @@ void backup_thread(const shared_ptr<HttpServer::Response>& response, const share
}
//backup
if(path == "") path = BACKUP_PATH;
- if(path[0] == '/') path = path.substr(1, path.length() - 1);
- if(path[path.length() - 1] == '/') path = path.substr(0, path.length() - 1);
if(path == "." ){
cout << "Backup Path Can not be root or empty, Backup Failed!" << endl;
string error = "Failed to backup the database. Backup Path Can not be root or empty.";
@@ -5156,6 +5154,9 @@ void backup_thread(const shared_ptr<HttpServer::Response>& response, const share
pthread_rwlock_unlock(&(it_already_build->second->db_lock));
return;
}
+ if(path[0] == '.') path = path.substr(1, path.length() - 1);
+ if(path[0] == '/') path = path.substr(1, path.length() - 1);
+ if(path[path.length() - 1] == '/') path = path.substr(0, path.length() - 1);
string db_path = db_name + ".db";
pthread_rwlock_wrlock(&databases_map_lock);
int ret = copy(db_path, path);