backup
This commit is contained in:
Родитель
303d1b287b
Коммит
b5a0194505
|
@ -0,0 +1,132 @@
|
|||
import { StorageConstants, UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
import { PortfolioTelemetry } from "../Utilities/Telemetry";
|
||||
import { ExtensionStorageError, IQueryResultError } from "../../Models/PortfolioPlanningQueryModels";
|
||||
import { ProgressTrackingUserSetting, RollupHierachyUserSetting } from "../../Contracts";
|
||||
|
||||
export class UserSettingsDataService {
|
||||
private static _instance: UserSettingsDataService;
|
||||
private _vsid: string = null;
|
||||
private _telemetry: PortfolioTelemetry;
|
||||
|
||||
private constructor() {
|
||||
try {
|
||||
const webContext = VSS.getWebContext();
|
||||
this._vsid = webContext.user.id;
|
||||
this._telemetry = PortfolioTelemetry.getInstance();
|
||||
} catch (error) {
|
||||
PortfolioTelemetry.getInstance().TrackException(error);
|
||||
} finally {
|
||||
// set default values.
|
||||
this._vsid = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static getInstance(): UserSettingsDataService {
|
||||
if (!UserSettingsDataService._instance) {
|
||||
UserSettingsDataService._instance = new UserSettingsDataService();
|
||||
}
|
||||
return UserSettingsDataService._instance;
|
||||
}
|
||||
|
||||
public async getUserSettings(): Promise<UserSettings> {
|
||||
if (!this._vsid) {
|
||||
this._telemetry.TrackAction("UserSettingsDataService/NullVSID");
|
||||
return Promise.resolve(this.getDefaultUserSettings());
|
||||
}
|
||||
|
||||
const client = await this.getStorageClient();
|
||||
let settings: UserSettings = this.getDefaultUserSettings();
|
||||
let document: any = null;
|
||||
|
||||
try {
|
||||
document = await client.getDocument(StorageConstants.COLLECTION_NAME, this._vsid);
|
||||
settings = this.parseUserSettingsDocument(document);
|
||||
} catch (error) {
|
||||
this._telemetry.TrackException(error);
|
||||
const parsedError = this.ParseStorageError(error);
|
||||
|
||||
if (parsedError.status === 404) {
|
||||
// Collection/document has not been created, initialize it.
|
||||
try {
|
||||
await client.setDocument(StorageConstants.COLLECTION_NAME, settings);
|
||||
} catch (error) {
|
||||
this._telemetry.TrackException(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
public async updateUserSettings(latestSettings: UserSettings): Promise<void> {
|
||||
if (!this._vsid) {
|
||||
this._telemetry.TrackAction("UserSettingsDataService/updateUserSettings/NullVSID");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const client = await this.getStorageClient();
|
||||
latestSettings.id = this._vsid;
|
||||
|
||||
try {
|
||||
await client.updateDocument(StorageConstants.COLLECTION_NAME, latestSettings);
|
||||
} catch (error) {
|
||||
this._telemetry.TrackException(error);
|
||||
}
|
||||
}
|
||||
|
||||
public getDefaultUserSettings(): UserSettings {
|
||||
return {
|
||||
Schema: StorageConstants.CURRENT_USER_SETTINGS_SCHEMA_VERSION,
|
||||
id: this._vsid,
|
||||
ProgressTrackingOption: this.getDefaultProgressTrackingOption(),
|
||||
TimelineItemRollup: this.getDefaultTimelineItemRollup()
|
||||
};
|
||||
}
|
||||
|
||||
private async getStorageClient(): Promise<IExtensionDataService> {
|
||||
return VSS.getService<IExtensionDataService>(VSS.ServiceIds.ExtensionData);
|
||||
}
|
||||
|
||||
private parseUserSettingsDocument(doc: any): UserSettings {
|
||||
if (!doc) {
|
||||
this._telemetry.TrackAction("UserSettingsDataService/ParseUserSettings/NoDoc");
|
||||
return this.getDefaultUserSettings();
|
||||
}
|
||||
|
||||
const settings: UserSettings = doc;
|
||||
|
||||
// Set default values if not present.
|
||||
if (!settings.ProgressTrackingOption) {
|
||||
settings.ProgressTrackingOption = this.getDefaultProgressTrackingOption();
|
||||
}
|
||||
|
||||
if (!settings.TimelineItemRollup) {
|
||||
settings.TimelineItemRollup = this.getDefaultTimelineItemRollup();
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private ParseStorageError(error: any): IQueryResultError {
|
||||
if (!error) {
|
||||
return {
|
||||
exceptionMessage: "no error information"
|
||||
};
|
||||
}
|
||||
|
||||
const parsedError: ExtensionStorageError = error;
|
||||
|
||||
return {
|
||||
exceptionMessage: parsedError.message,
|
||||
status: parsedError.status
|
||||
};
|
||||
}
|
||||
|
||||
private getDefaultProgressTrackingOption(): ProgressTrackingUserSetting.Options {
|
||||
return ProgressTrackingUserSetting.Options.CompletedCount;
|
||||
}
|
||||
|
||||
private getDefaultTimelineItemRollup(): RollupHierachyUserSetting.Options {
|
||||
return RollupHierachyUserSetting.Options.Descendants;
|
||||
}
|
||||
}
|
|
@ -138,6 +138,7 @@ export class PortfolioTelemetry {
|
|||
|
||||
public TrackException(exception: Error) {
|
||||
try {
|
||||
console.log(JSON.stringify(exception, null, " "));
|
||||
AppInsightsClient.getAppInsightsInstance().trackException({
|
||||
error: exception,
|
||||
properties: {
|
||||
|
|
|
@ -4,7 +4,7 @@ import "./DependencyPanel.scss";
|
|||
import {
|
||||
ITimelineItem,
|
||||
LoadingStatus,
|
||||
ProgressTrackingCriteria,
|
||||
ProgressTrackingUserSetting,
|
||||
IWorkItemIcon,
|
||||
IProject,
|
||||
IProjectConfiguration
|
||||
|
@ -27,13 +27,14 @@ import { MessageCard, MessageCardSeverity } from "azure-devops-ui/MessageCard";
|
|||
import { connect } from "react-redux";
|
||||
import { Icon } from "azure-devops-ui/Icon";
|
||||
import moment = require("moment");
|
||||
import { UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
|
||||
type WorkItemIconMap = { [projectIdKey: string]: { [workItemType: string]: IWorkItemIcon } };
|
||||
type WorkItemInProgressStatesMap = { [projectIdKey: string]: { [WorkItemType: string]: string[] } };
|
||||
export interface IDependencyPanelProps {
|
||||
workItem: ITimelineItem;
|
||||
projectInfo: IProject;
|
||||
progressTrackingCriteria: ProgressTrackingCriteria;
|
||||
userSettings: UserSettings;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
|
@ -279,11 +280,13 @@ export class DependencyPanel extends React.Component<IDependencyPanelProps, IDep
|
|||
projectId: dependency.ProjectId,
|
||||
workItemType: dependency.WorkItemType,
|
||||
completed:
|
||||
this.props.progressTrackingCriteria === ProgressTrackingCriteria.CompletedCount
|
||||
this.props.userSettings.ProgressTrackingOption ===
|
||||
ProgressTrackingUserSetting.CompletedCount.Key
|
||||
? dependency.CompletedCount
|
||||
: dependency.CompletedEffort,
|
||||
total:
|
||||
this.props.progressTrackingCriteria === ProgressTrackingCriteria.CompletedCount
|
||||
this.props.userSettings.ProgressTrackingOption ===
|
||||
ProgressTrackingUserSetting.CompletedCount.Key
|
||||
? dependency.TotalCount
|
||||
: dependency.TotalEffort,
|
||||
showInfoIcon: this._showInfoIcon(dependency, isPredecessor),
|
||||
|
@ -408,7 +411,7 @@ export class DependencyPanel extends React.Component<IDependencyPanelProps, IDep
|
|||
|
||||
const mapStateToProps = (state: IDependencyPanelState, ownProps: IDependencyPanelProps) => ({
|
||||
workItem: ownProps.workItem,
|
||||
progressTrackingCriteria: ownProps.progressTrackingCriteria,
|
||||
userSettings: ownProps.userSettings,
|
||||
onDismiss: ownProps.onDismiss
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import { PlanDirectoryActions } from "../../Redux/Actions/PlanDirectoryActions";
|
|||
import { EpicTimelineActions } from "../../Redux/Actions/EpicTimelineActions";
|
||||
import { PortfolioPlanningMetadata } from "../../Models/PortfolioPlanningQueryModels";
|
||||
import { PlanSettingsPanel } from "./PlanSettingsPanel";
|
||||
import { ProgressTrackingCriteria, ITimelineItem, LoadingStatus } from "../../Contracts";
|
||||
import { ITimelineItem, LoadingStatus } from "../../Contracts";
|
||||
import { AddItemPanel } from "./AddItemPanel";
|
||||
import { Spinner, SpinnerSize } from "azure-devops-ui/Spinner";
|
||||
import { Link } from "azure-devops-ui/Link";
|
||||
|
@ -25,6 +25,7 @@ import { DeletePlanDialog } from "./DeletePlanDialog";
|
|||
import { MessageCard, MessageCardSeverity } from "azure-devops-ui/MessageCard";
|
||||
import { PortfolioTelemetry } from "../../Common/Utilities/Telemetry";
|
||||
import { ExtendedSinglePlanTelemetry } from "../../Models/TelemetryModels";
|
||||
import { UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
|
||||
interface IPlanPageMappedProps {
|
||||
plan: PortfolioPlanningMetadata;
|
||||
|
@ -32,7 +33,6 @@ interface IPlanPageMappedProps {
|
|||
teamNames: string[];
|
||||
epicIds: { [epicId: number]: number };
|
||||
selectedItem: ITimelineItem;
|
||||
progressTrackingCriteria: ProgressTrackingCriteria;
|
||||
addItemPanelOpen: boolean;
|
||||
planSettingsPanelOpen: boolean;
|
||||
exceptionMessage: string;
|
||||
|
@ -40,6 +40,7 @@ interface IPlanPageMappedProps {
|
|||
isNewPlanExperience: boolean;
|
||||
deletePlanDialogHidden: boolean;
|
||||
planTelemetry: ExtendedSinglePlanTelemetry;
|
||||
userSettings: UserSettings;
|
||||
}
|
||||
|
||||
export type IPlanPageProps = IPlanPageMappedProps & typeof Actions;
|
||||
|
@ -143,8 +144,9 @@ export default class PlanPage extends React.Component<IPlanPageProps, IPortfolio
|
|||
if (this.props.planSettingsPanelOpen) {
|
||||
return (
|
||||
<PlanSettingsPanel
|
||||
progressTrackingCriteria={this.props.progressTrackingCriteria}
|
||||
userSettings={this.props.userSettings}
|
||||
onProgressTrackingCriteriaChanged={this.props.onToggleProgressTrackingCriteria}
|
||||
onTimelineItemRollupChanged={this.props.onToggleTimelineItemRollupCriteria}
|
||||
onClosePlanSettingsPanel={() => {
|
||||
this.props.onTogglePlanSettingsPanelOpen(false);
|
||||
}}
|
||||
|
@ -186,15 +188,15 @@ function mapStateToProps(state: IPortfolioPlanningState): IPlanPageMappedProps {
|
|||
projectNames: getProjectNames(state),
|
||||
teamNames: getTeamNames(state),
|
||||
epicIds: getEpicIds(state.epicTimelineState),
|
||||
selectedItem: getSelectedItem(state.epicTimelineState),
|
||||
progressTrackingCriteria: state.epicTimelineState.progressTrackingCriteria,
|
||||
selectedItem: getSelectedItem(state.epicTimelineState, state.planDirectoryState.userSettings),
|
||||
addItemPanelOpen: state.epicTimelineState.addItemsPanelOpen,
|
||||
planSettingsPanelOpen: state.epicTimelineState.planSettingsPanelOpen,
|
||||
exceptionMessage: state.epicTimelineState.exceptionMessage,
|
||||
planLoadingStatus: state.epicTimelineState.planLoadingStatus,
|
||||
isNewPlanExperience: state.epicTimelineState.isNewPlanExperience,
|
||||
deletePlanDialogHidden: state.epicTimelineState.deletePlanDialogHidden,
|
||||
planTelemetry: getPlanExtendedTelemetry(state.epicTimelineState)
|
||||
planTelemetry: getPlanExtendedTelemetry(state.epicTimelineState),
|
||||
userSettings: state.planDirectoryState.userSettings
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -204,9 +206,13 @@ const Actions = {
|
|||
resetPlanState: EpicTimelineActions.resetPlanState,
|
||||
onOpenAddItemPanel: EpicTimelineActions.openAddItemPanel,
|
||||
onToggleProgressTrackingCriteria: EpicTimelineActions.toggleProgressTrackingCriteria,
|
||||
onToggleTimelineItemRollupCriteria: EpicTimelineActions.toggleTimelineRollupCriteria,
|
||||
onCloseAddItemPanel: EpicTimelineActions.closeAddItemPanel,
|
||||
onAddItems: EpicTimelineActions.addItems,
|
||||
onTogglePlanSettingsPanelOpen: EpicTimelineActions.togglePlanSettingsPanelOpen,
|
||||
|
||||
// TODO Need another action when the panel is closed.
|
||||
// Check if settings changed, and reload timeline if needed.
|
||||
toggleDeletePlanDialogHidden: EpicTimelineActions.toggleDeletePlanDialogHidden,
|
||||
dismissErrorMessageCard: EpicTimelineActions.dismissErrorMessageCard
|
||||
};
|
||||
|
|
|
@ -2,21 +2,17 @@ import * as React from "react";
|
|||
import "./PlanSettingsPanel.scss";
|
||||
import { Panel } from "azure-devops-ui/Panel";
|
||||
import { ComboBox } from "office-ui-fabric-react/lib/ComboBox";
|
||||
import { ProgressTrackingCriteria } from "../../Contracts";
|
||||
import { UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
import { ProgressTrackingUserSetting, RollupHierachyUserSetting } from "../../Contracts";
|
||||
|
||||
export interface IPlanSettingsProps {
|
||||
progressTrackingCriteria: ProgressTrackingCriteria;
|
||||
onProgressTrackingCriteriaChanged: (criteria: ProgressTrackingCriteria) => void;
|
||||
userSettings: UserSettings;
|
||||
onProgressTrackingCriteriaChanged: (criteria: ProgressTrackingUserSetting.Options) => void;
|
||||
onTimelineItemRollupChanged: (criteria: RollupHierachyUserSetting.Options) => void;
|
||||
onClosePlanSettingsPanel: () => void;
|
||||
}
|
||||
|
||||
export const PlanSettingsPanel = (props: IPlanSettingsProps) => {
|
||||
const completedCountKey = "completedCount";
|
||||
const effortKey = "effort";
|
||||
|
||||
const selectedProgressCriteriaKey =
|
||||
props.progressTrackingCriteria === ProgressTrackingCriteria.CompletedCount ? completedCountKey : effortKey;
|
||||
|
||||
return (
|
||||
<Panel onDismiss={props.onClosePlanSettingsPanel} titleProps={{ text: "Settings" }}>
|
||||
<div className="settings-container">
|
||||
|
@ -24,28 +20,43 @@ export const PlanSettingsPanel = (props: IPlanSettingsProps) => {
|
|||
<div className="progress-options-label">Track Progress Using: </div>
|
||||
<ComboBox
|
||||
className="progress-options-dropdown"
|
||||
selectedKey={selectedProgressCriteriaKey}
|
||||
selectedKey={props.userSettings.ProgressTrackingOption}
|
||||
allowFreeform={false}
|
||||
autoComplete="off"
|
||||
options={[
|
||||
{
|
||||
key: completedCountKey,
|
||||
text: ProgressTrackingCriteria.CompletedCount
|
||||
key: ProgressTrackingUserSetting.CompletedCount.Key,
|
||||
text: ProgressTrackingUserSetting.CompletedCount.Text
|
||||
},
|
||||
{
|
||||
key: effortKey,
|
||||
text: ProgressTrackingCriteria.Effort
|
||||
key: ProgressTrackingUserSetting.Effort.Key,
|
||||
text: ProgressTrackingUserSetting.Effort.Text
|
||||
}
|
||||
]}
|
||||
onChanged={(item: { key: string; text: string }) => {
|
||||
switch (item.key) {
|
||||
case completedCountKey:
|
||||
props.onProgressTrackingCriteriaChanged(ProgressTrackingCriteria.CompletedCount);
|
||||
break;
|
||||
case effortKey:
|
||||
props.onProgressTrackingCriteriaChanged(ProgressTrackingCriteria.Effort);
|
||||
break;
|
||||
props.onProgressTrackingCriteriaChanged(item.key as ProgressTrackingUserSetting.Options);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="progress-options settings-item">
|
||||
<div className="progress-options-label">Track Progress Using: </div>
|
||||
<ComboBox
|
||||
className="progress-options-dropdown"
|
||||
selectedKey={props.userSettings.TimelineItemRollup}
|
||||
allowFreeform={false}
|
||||
autoComplete="off"
|
||||
options={[
|
||||
{
|
||||
key: RollupHierachyUserSetting.Children.Key,
|
||||
text: RollupHierachyUserSetting.Children.Text
|
||||
},
|
||||
{
|
||||
key: RollupHierachyUserSetting.Descendants.Key,
|
||||
text: RollupHierachyUserSetting.Descendants.Text
|
||||
}
|
||||
]}
|
||||
onChanged={(item: { key: string; text: string }) => {
|
||||
props.onTimelineItemRollupChanged(item.key as RollupHierachyUserSetting.Options);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from "react";
|
||||
import * as moment from "moment";
|
||||
import { ITimelineGroup, ITimelineItem, ITeam, ProgressTrackingCriteria, IProject } from "../../Contracts";
|
||||
import { ITimelineGroup, ITimelineItem, ITeam, IProject } from "../../Contracts";
|
||||
import Timeline, { TimelineHeaders, DateHeader } from "react-calendar-timeline";
|
||||
import "./PlanTimeline.scss";
|
||||
import { IPortfolioPlanningState } from "../../Redux/Contracts";
|
||||
|
@ -26,6 +26,7 @@ import { MenuButton } from "azure-devops-ui/Menu";
|
|||
import { IconSize } from "azure-devops-ui/Icon";
|
||||
import { DetailsDialog } from "./DetailsDialog";
|
||||
import { ConnectedDependencyPanel } from "./DependencyPanel";
|
||||
import { UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
|
||||
const day = 60 * 60 * 24 * 1000;
|
||||
const week = day * 7;
|
||||
|
@ -52,8 +53,8 @@ interface IPlanTimelineMappedProps {
|
|||
planOwner: IdentityRef;
|
||||
exceptionMessage: string;
|
||||
setDatesDialogHidden: boolean;
|
||||
progressTrackingCriteria: ProgressTrackingCriteria;
|
||||
projects: { [projectIdKey: string]: IProject };
|
||||
userSettings: UserSettings;
|
||||
}
|
||||
|
||||
interface IPlanTimelineState {
|
||||
|
@ -133,7 +134,7 @@ export class PlanTimeline extends React.Component<IPlanTimelineProps, IPlanTimel
|
|||
<ConnectedDependencyPanel
|
||||
workItem={this.state.contextMenuItem}
|
||||
projectInfo={this.props.projects[this.state.contextMenuItem.projectId.toLowerCase()]}
|
||||
progressTrackingCriteria={this.props.progressTrackingCriteria}
|
||||
userSettings={this.props.userSettings}
|
||||
onDismiss={() => this.setState({ dependencyPanelOpen: false })}
|
||||
/>
|
||||
);
|
||||
|
@ -565,13 +566,13 @@ function mapStateToProps(state: IPortfolioPlanningState): IPlanTimelineMappedPro
|
|||
projectNames: getProjectNames(state),
|
||||
teamNames: getTeamNames(state),
|
||||
teams: state.epicTimelineState.teams,
|
||||
items: getTimelineItems(state.epicTimelineState),
|
||||
items: getTimelineItems(state.epicTimelineState, state.planDirectoryState.userSettings),
|
||||
selectedItemId: state.epicTimelineState.selectedItemId,
|
||||
planOwner: getSelectedPlanOwner(state),
|
||||
exceptionMessage: state.epicTimelineState.exceptionMessage,
|
||||
setDatesDialogHidden: state.epicTimelineState.setDatesDialogHidden,
|
||||
progressTrackingCriteria: state.epicTimelineState.progressTrackingCriteria,
|
||||
projects: getIndexedProjects(state.epicTimelineState)
|
||||
projects: getIndexedProjects(state.epicTimelineState),
|
||||
userSettings: state.planDirectoryState.userSettings
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -112,9 +112,43 @@ export interface ITimelineItem {
|
|||
canMove: boolean;
|
||||
}
|
||||
|
||||
export enum ProgressTrackingCriteria {
|
||||
CompletedCount = "Completed Count",
|
||||
Effort = "Effort"
|
||||
export interface IUseSettingKeyTextPair {
|
||||
Key: ProgressTrackingUserSetting.Options | RollupHierachyUserSetting.Options;
|
||||
Text: string;
|
||||
}
|
||||
|
||||
export namespace ProgressTrackingUserSetting {
|
||||
export enum Options {
|
||||
CompletedCount = "completedcount",
|
||||
Effort = "effort"
|
||||
}
|
||||
|
||||
export const CompletedCount: IUseSettingKeyTextPair = {
|
||||
Key: Options.CompletedCount,
|
||||
Text: "Completed Count"
|
||||
};
|
||||
|
||||
export const Effort: IUseSettingKeyTextPair = {
|
||||
Key: Options.Effort,
|
||||
Text: "Effort"
|
||||
};
|
||||
}
|
||||
|
||||
export namespace RollupHierachyUserSetting {
|
||||
export enum Options {
|
||||
Children = "children",
|
||||
Descendants = "descendants"
|
||||
}
|
||||
|
||||
export const Children: IUseSettingKeyTextPair = {
|
||||
Key: Options.Descendants,
|
||||
Text: "Children"
|
||||
};
|
||||
|
||||
export const Descendants: IUseSettingKeyTextPair = {
|
||||
Key: Options.Descendants,
|
||||
Text: "Descendants"
|
||||
};
|
||||
}
|
||||
|
||||
export enum LoadingStatus {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { RollupHierachyUserSetting, ProgressTrackingUserSetting } from "../Contracts";
|
||||
|
||||
export class StorageConstants {
|
||||
public static COLLECTION_NAME: string = "UserSettings";
|
||||
public static CURRENT_USER_SETTINGS_SCHEMA_VERSION: number = 1;
|
||||
}
|
||||
|
||||
export interface UserSettings {
|
||||
Schema: number;
|
||||
id: string;
|
||||
ProgressTrackingOption: ProgressTrackingUserSetting.Options;
|
||||
TimelineItemRollup: RollupHierachyUserSetting.Options;
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
import { createAction, ActionsUnion } from "../Helpers";
|
||||
import {
|
||||
ProgressTrackingCriteria,
|
||||
ProgressTrackingUserSetting,
|
||||
IAddItems,
|
||||
IRemoveItem,
|
||||
LoadingStatus,
|
||||
IProjectConfiguration
|
||||
IProjectConfiguration,
|
||||
RollupHierachyUserSetting
|
||||
} from "../../Contracts";
|
||||
import moment = require("moment");
|
||||
import { PortfolioPlanningFullContentQueryResult } from "../../Models/PortfolioPlanningQueryModels";
|
||||
|
@ -24,6 +25,7 @@ export const enum EpicTimelineActionTypes {
|
|||
AddItems = "EpicTimeline/AddItems",
|
||||
RemoveItems = "EpicTimeline/RemoveItems",
|
||||
ToggleProgressTrackingCriteria = "EpicTimeline/ToggleProgressTrackingCriteria",
|
||||
ToggleTimelineRollupCriteria = "EpicTimeline/ToggleTimelineRollupCriteria",
|
||||
ToggleLoadingStatus = "EpicTimeline/ToggleLoadingStatus",
|
||||
ResetPlanState = "EpicTimeline/ResetPlanState",
|
||||
TogglePlanSettingsPanelOpen = "EpicTimeline/TogglePlanSettingsPanelOpen",
|
||||
|
@ -74,10 +76,22 @@ export const EpicTimelineActions = {
|
|||
PortfolioTelemetry.getInstance().TrackAction(EpicTimelineActionTypes.RemoveItems);
|
||||
return createAction(EpicTimelineActionTypes.RemoveItems, itemToRemove);
|
||||
},
|
||||
toggleProgressTrackingCriteria: (criteria: ProgressTrackingCriteria) =>
|
||||
createAction(EpicTimelineActionTypes.ToggleProgressTrackingCriteria, {
|
||||
toggleProgressTrackingCriteria: (criteria: ProgressTrackingUserSetting.Options) => {
|
||||
PortfolioTelemetry.getInstance().TrackAction(EpicTimelineActionTypes.ToggleProgressTrackingCriteria, {
|
||||
["Value"]: criteria
|
||||
});
|
||||
return createAction(EpicTimelineActionTypes.ToggleProgressTrackingCriteria, {
|
||||
criteria
|
||||
}),
|
||||
});
|
||||
},
|
||||
toggleTimelineRollupCriteria: (criteria: RollupHierachyUserSetting.Options) => {
|
||||
PortfolioTelemetry.getInstance().TrackAction(EpicTimelineActionTypes.ToggleTimelineRollupCriteria, {
|
||||
["Value"]: criteria
|
||||
});
|
||||
return createAction(EpicTimelineActionTypes.ToggleTimelineRollupCriteria, {
|
||||
criteria
|
||||
});
|
||||
},
|
||||
toggleLoadingStatus: (status: LoadingStatus) =>
|
||||
createAction(EpicTimelineActionTypes.ToggleLoadingStatus, { status }),
|
||||
resetPlanState: () => createAction(EpicTimelineActionTypes.ResetPlanState),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { createAction, ActionsUnion } from "../Helpers";
|
||||
import { PortfolioPlanningDirectory, PortfolioPlanning } from "../../Models/PortfolioPlanningQueryModels";
|
||||
import { PortfolioTelemetry } from "../../Common/Utilities/Telemetry";
|
||||
import { UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
|
||||
export const enum PlanDirectoryActionTypes {
|
||||
Initialize = "PlanDirectory/Initialize",
|
||||
|
@ -16,8 +17,8 @@ export const enum PlanDirectoryActionTypes {
|
|||
}
|
||||
|
||||
export const PlanDirectoryActions = {
|
||||
initialize: (directoryData: PortfolioPlanningDirectory) =>
|
||||
createAction(PlanDirectoryActionTypes.Initialize, { directoryData }),
|
||||
initialize: (directoryData: PortfolioPlanningDirectory, userSettings: UserSettings) =>
|
||||
createAction(PlanDirectoryActionTypes.Initialize, { directoryData, userSettings }),
|
||||
createPlan: (name: string, description: string) => {
|
||||
PortfolioTelemetry.getInstance().TrackAction(PlanDirectoryActionTypes.CreatePlan);
|
||||
return createAction(PlanDirectoryActionTypes.CreatePlan, {
|
||||
|
@ -43,7 +44,7 @@ export const PlanDirectoryActions = {
|
|||
PortfolioTelemetry.getInstance().TrackAction(PlanDirectoryActionTypes.DeletePlan);
|
||||
return createAction(PlanDirectoryActionTypes.DeletePlan, { id });
|
||||
},
|
||||
updateProjectsAndTeamsMetadata: (planId:string, projectNames: string[], teamNames: string[]) =>
|
||||
updateProjectsAndTeamsMetadata: (planId: string, projectNames: string[], teamNames: string[]) =>
|
||||
createAction(PlanDirectoryActionTypes.UpdateProjectsAndTeamsMetadata, { planId, projectNames, teamNames }),
|
||||
toggleSelectedPlanId: (id: string) => {
|
||||
PortfolioTelemetry.getInstance().TrackAction(PlanDirectoryActionTypes.ToggleSelectedPlanId);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { IProject, IWorkItem, ProgressTrackingCriteria, ITeam, LoadingStatus } from "../Contracts";
|
||||
import { IProject, IWorkItem, ITeam, LoadingStatus } from "../Contracts";
|
||||
import { PortfolioPlanningMetadata } from "../Models/PortfolioPlanningQueryModels";
|
||||
import { ExtendedSinglePlanTelemetry } from "../Models/TelemetryModels";
|
||||
import { UserSettings } from "../Models/UserSettingsDataModels";
|
||||
|
||||
export interface IPortfolioPlanningState {
|
||||
planDirectoryState: IPlanDirectoryState;
|
||||
|
@ -18,10 +19,10 @@ export interface IEpicTimelineState {
|
|||
setDatesDialogHidden: boolean;
|
||||
planSettingsPanelOpen: boolean;
|
||||
selectedItemId: number;
|
||||
progressTrackingCriteria: ProgressTrackingCriteria;
|
||||
isNewPlanExperience: boolean;
|
||||
deletePlanDialogHidden: boolean;
|
||||
planTelemetry: ExtendedSinglePlanTelemetry;
|
||||
userSettings: UserSettings;
|
||||
}
|
||||
|
||||
export interface IPlanDirectoryState {
|
||||
|
@ -30,4 +31,5 @@ export interface IPlanDirectoryState {
|
|||
selectedPlanId: string;
|
||||
newPlanDialogVisible: boolean;
|
||||
plans: PortfolioPlanningMetadata[];
|
||||
userSettings: UserSettings;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ import {
|
|||
PortfolioItemDeletedAction
|
||||
} from "../Actions/EpicTimelineActions";
|
||||
import produce from "immer";
|
||||
import { ProgressTrackingCriteria, LoadingStatus, IWorkItemIcon } from "../../Contracts";
|
||||
import { LoadingStatus, IWorkItemIcon } from "../../Contracts";
|
||||
import { MergeType } from "../../Models/PortfolioPlanningQueryModels";
|
||||
import { defaultIProjectComparer, defaultIWorkItemComparer } from "../../Common/Utilities/Comparers";
|
||||
import { UserSettingsDataService } from "../../Common/Services/UserSettingsDataService";
|
||||
|
||||
export function epicTimelineReducer(state: IEpicTimelineState, action: EpicTimelineActions): IEpicTimelineState {
|
||||
return produce(state || getDefaultState(), (draft: IEpicTimelineState) => {
|
||||
|
@ -79,7 +80,7 @@ export function epicTimelineReducer(state: IEpicTimelineState, action: EpicTimel
|
|||
return handlePortfolioItemDeleted(state, action as PortfolioItemDeletedAction);
|
||||
}
|
||||
case EpicTimelineActionTypes.ToggleProgressTrackingCriteria: {
|
||||
draft.progressTrackingCriteria = action.payload.criteria;
|
||||
draft.userSettings.ProgressTrackingOption = action.payload.criteria;
|
||||
break;
|
||||
}
|
||||
case EpicTimelineActionTypes.ToggleLoadingStatus: {
|
||||
|
@ -142,10 +143,10 @@ export function getDefaultState(): IEpicTimelineState {
|
|||
setDatesDialogHidden: true,
|
||||
planSettingsPanelOpen: false,
|
||||
selectedItemId: null,
|
||||
progressTrackingCriteria: ProgressTrackingCriteria.CompletedCount,
|
||||
isNewPlanExperience: false,
|
||||
deletePlanDialogHidden: true,
|
||||
planTelemetry: null
|
||||
planTelemetry: null,
|
||||
userSettings: UserSettingsDataService.getInstance().getDefaultUserSettings()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -3,17 +3,19 @@ import produce from "immer";
|
|||
import { PlanDirectoryActions, PlanDirectoryActionTypes } from "../Actions/PlanDirectoryActions";
|
||||
import { LoadingStatus } from "../../Contracts";
|
||||
import { caseInsensitiveComparer } from "../../Common/Utilities/String";
|
||||
import { UserSettingsDataService } from "../../Common/Services/UserSettingsDataService";
|
||||
|
||||
export function planDirectoryReducer(state: IPlanDirectoryState, action: PlanDirectoryActions): IPlanDirectoryState {
|
||||
return produce(state || getDefaultState(), (draft: IPlanDirectoryState) => {
|
||||
switch (action.type) {
|
||||
case PlanDirectoryActionTypes.Initialize: {
|
||||
const { directoryData } = action.payload;
|
||||
const { directoryData, userSettings } = action.payload;
|
||||
|
||||
draft.directoryLoadingStatus = LoadingStatus.Loaded;
|
||||
draft.exceptionMessage = directoryData.exceptionMessage;
|
||||
draft.plans = directoryData.entries;
|
||||
draft.plans.sort((a, b) => caseInsensitiveComparer(a.name, b.name));
|
||||
draft.userSettings = userSettings;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -83,6 +85,7 @@ export function getDefaultState(): IPlanDirectoryState {
|
|||
exceptionMessage: "",
|
||||
selectedPlanId: undefined,
|
||||
plans: [],
|
||||
newPlanDialogVisible: false
|
||||
newPlanDialogVisible: false,
|
||||
userSettings: UserSettingsDataService.getInstance().getDefaultUserSettings()
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ import { PlanDirectoryActionTypes, PlanDirectoryActions } from "../Actions/PlanD
|
|||
import { LoadPortfolio } from "./LoadPortfolio";
|
||||
import { ActionsOfType } from "../Helpers";
|
||||
import { SetDefaultDatesForWorkItems, saveDatesToServer } from "./DefaultDateUtil";
|
||||
import { UserSettingsDataService } from "../../Common/Services/UserSettingsDataService";
|
||||
import { UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
import { getUserSettings } from "../Selectors/PlanDirectorySelectors";
|
||||
|
||||
export function* epicTimelineSaga(): SagaIterator {
|
||||
yield takeEvery(EpicTimelineActionTypes.UpdateDates, onUpdateDates);
|
||||
|
@ -24,11 +27,45 @@ export function* epicTimelineSaga(): SagaIterator {
|
|||
yield takeEvery(EpicTimelineActionTypes.AddItems, onAddEpics);
|
||||
yield takeEvery(PlanDirectoryActionTypes.ToggleSelectedPlanId, onToggleSelectedPlanId);
|
||||
yield takeEvery(EpicTimelineActionTypes.RemoveItems, onRemoveEpic);
|
||||
yield takeEvery(EpicTimelineActionTypes.ToggleProgressTrackingCriteria, onProgressTrackingCriteria);
|
||||
yield takeEvery(EpicTimelineActionTypes.ToggleTimelineRollupCriteria, onTimelineRollupCriteria);
|
||||
}
|
||||
|
||||
function* onUpdateDates(
|
||||
action: ActionsOfType<EpicTimelineActions, EpicTimelineActionTypes.UpdateDates>
|
||||
function* onProgressTrackingCriteria(
|
||||
action: ActionsOfType<EpicTimelineActions, EpicTimelineActionTypes.ToggleProgressTrackingCriteria>
|
||||
): SagaIterator {
|
||||
const newOption = action.payload.criteria;
|
||||
try {
|
||||
const dataService = UserSettingsDataService.getInstance();
|
||||
const userSettings: UserSettings = yield effects.select(getUserSettings);
|
||||
|
||||
userSettings.ProgressTrackingOption = newOption;
|
||||
|
||||
yield effects.call([dataService, dataService.updateUserSettings], userSettings);
|
||||
} catch (exception) {
|
||||
console.error(exception);
|
||||
yield effects.put(EpicTimelineActions.handleGeneralException(exception));
|
||||
}
|
||||
}
|
||||
|
||||
function* onTimelineRollupCriteria(
|
||||
action: ActionsOfType<EpicTimelineActions, EpicTimelineActionTypes.ToggleTimelineRollupCriteria>
|
||||
): SagaIterator {
|
||||
const newOption = action.payload.criteria;
|
||||
try {
|
||||
const dataService = UserSettingsDataService.getInstance();
|
||||
const userSettings: UserSettings = yield effects.select(getUserSettings);
|
||||
|
||||
userSettings.TimelineItemRollup = newOption;
|
||||
|
||||
yield effects.call([dataService, dataService.updateUserSettings], userSettings);
|
||||
} catch (exception) {
|
||||
console.error(exception);
|
||||
yield effects.put(EpicTimelineActions.handleGeneralException(exception));
|
||||
}
|
||||
}
|
||||
|
||||
function* onUpdateDates(action: ActionsOfType<EpicTimelineActions, EpicTimelineActionTypes.UpdateDates>): SagaIterator {
|
||||
const epicId = action.payload.epicId;
|
||||
try {
|
||||
yield effects.call(saveDatesToServer, epicId);
|
||||
|
@ -51,6 +88,7 @@ function* onShiftEpic(action: ActionsOfType<EpicTimelineActions, EpicTimelineAct
|
|||
yield effects.put(EpicTimelineActions.updateItemFinished(epicId));
|
||||
}
|
||||
}
|
||||
|
||||
function* onAddEpics(action: ActionsOfType<EpicTimelineActions, EpicTimelineActionTypes.AddItems>): SagaIterator {
|
||||
try {
|
||||
yield effects.put(EpicTimelineActions.toggleLoadingStatus(LoadingStatus.NotLoaded));
|
||||
|
|
|
@ -14,6 +14,8 @@ import { getProjectNames, getTeamNames, getExceptionMessage } from "../Selectors
|
|||
import { PortfolioTelemetry } from "../../Common/Utilities/Telemetry";
|
||||
import { LaunchWorkItemFormActionType } from "../../../Common/redux/actions/launchWorkItemForm";
|
||||
import { launchWorkItemFormSaga } from "./launchWorkItemFromSaga";
|
||||
import { UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
import { UserSettingsDataService } from "../../Common/Services/UserSettingsDataService";
|
||||
|
||||
export function* planDirectorySaga(): SagaIterator {
|
||||
yield effects.call(initializePlanDirectory);
|
||||
|
@ -21,16 +23,18 @@ export function* planDirectorySaga(): SagaIterator {
|
|||
yield effects.takeEvery(PlanDirectoryActionTypes.DeletePlan, deletePlan);
|
||||
yield effects.takeEvery(EpicTimelineActionTypes.PortfolioItemsReceived, updateProjectsAndTeamsMetadata);
|
||||
yield effects.takeEvery(EpicTimelineActionTypes.PortfolioItemDeleted, updateProjectsAndTeamsMetadata);
|
||||
yield effects.takeEvery(LaunchWorkItemFormActionType, launchWorkItemFormSaga);
|
||||
yield effects.takeEvery(LaunchWorkItemFormActionType, launchWorkItemFormSaga);
|
||||
}
|
||||
|
||||
export function* initializePlanDirectory(): SagaIterator {
|
||||
try {
|
||||
const service = PortfolioPlanningDataService.getInstance();
|
||||
const settingsService = UserSettingsDataService.getInstance();
|
||||
|
||||
const allPlans: PortfolioPlanningDirectory = yield effects.call([service, service.GetAllPortfolioPlans]);
|
||||
const userSettings: UserSettings = yield effects.call([settingsService, settingsService.getUserSettings]);
|
||||
|
||||
yield effects.put(PlanDirectoryActions.initialize(allPlans));
|
||||
yield effects.put(PlanDirectoryActions.initialize(allPlans, userSettings));
|
||||
} catch (exception) {
|
||||
console.error(exception);
|
||||
yield effects.put(PlanDirectoryActions.handleGeneralException(exception));
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { IEpicTimelineState, IPortfolioPlanningState } from "../Contracts";
|
||||
import { IProject, IWorkItem, ITimelineGroup, ITimelineItem, ProgressTrackingCriteria } from "../../Contracts";
|
||||
import { IProject, IWorkItem, ITimelineGroup, ITimelineItem, ProgressTrackingUserSetting } from "../../Contracts";
|
||||
import moment = require("moment");
|
||||
import { ExtendedSinglePlanTelemetry } from "../../Models/TelemetryModels";
|
||||
import { UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
|
||||
export function getProjects(state: IEpicTimelineState): IProject[] {
|
||||
return state.projects;
|
||||
|
@ -53,13 +54,13 @@ export function getEpicIds(state: IEpicTimelineState): { [epicId: number]: numbe
|
|||
return result;
|
||||
}
|
||||
|
||||
export function getTimelineItems(state: IEpicTimelineState): ITimelineItem[] {
|
||||
export function getTimelineItems(state: IEpicTimelineState, userSettings: UserSettings): ITimelineItem[] {
|
||||
return state.epics.map(epic => {
|
||||
let completed: number;
|
||||
let total: number;
|
||||
let progress: number;
|
||||
|
||||
if (state.progressTrackingCriteria === ProgressTrackingCriteria.CompletedCount) {
|
||||
if (userSettings.ProgressTrackingOption === ProgressTrackingUserSetting.CompletedCount.Key) {
|
||||
completed = epic.completedCount;
|
||||
total = epic.totalCount;
|
||||
progress = epic.countProgress;
|
||||
|
@ -90,8 +91,8 @@ export function getTimelineItems(state: IEpicTimelineState): ITimelineItem[] {
|
|||
});
|
||||
}
|
||||
|
||||
export function getSelectedItem(state: IEpicTimelineState): ITimelineItem {
|
||||
return getTimelineItems(state).find(item => item.id === state.selectedItemId);
|
||||
export function getSelectedItem(state: IEpicTimelineState, userSettings: UserSettings): ITimelineItem {
|
||||
return getTimelineItems(state, userSettings).find(item => item.id === state.selectedItemId);
|
||||
}
|
||||
|
||||
export function getMessage(state: IEpicTimelineState): string {
|
||||
|
@ -117,10 +118,6 @@ export function getAddEpicPanelOpen(state: IEpicTimelineState): boolean {
|
|||
return state.addItemsPanelOpen;
|
||||
}
|
||||
|
||||
export function getProgressTrackingCriteria(state: IEpicTimelineState): ProgressTrackingCriteria {
|
||||
return state.progressTrackingCriteria;
|
||||
}
|
||||
|
||||
export function getExceptionMessage(state: IPortfolioPlanningState): string {
|
||||
return state.epicTimelineState.exceptionMessage;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { IPortfolioPlanningState, IPlanDirectoryState } from "../Contracts";
|
|||
import { IdentityRef } from "VSS/WebApi/Contracts";
|
||||
import { PortfolioPlanningMetadata } from "../../Models/PortfolioPlanningQueryModels";
|
||||
import { SinglePlanTelemetry } from "../../Models/TelemetryModels";
|
||||
import { UserSettings } from "../../Models/UserSettingsDataModels";
|
||||
|
||||
export function getSelectedPlanId(state: IPortfolioPlanningState): string {
|
||||
return state.planDirectoryState.selectedPlanId;
|
||||
|
@ -27,3 +28,7 @@ export function getPlansTelemetry(state: IPlanDirectoryState): SinglePlanTelemet
|
|||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function getUserSettings(state: IPlanDirectoryState): UserSettings {
|
||||
return state.userSettings;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче