Skip to content
Permalink
Browse files

Analyze menu (#69)

Changed how incidents are selected under "Analyze".
A new menu with more information
  • Loading branch information...
jgauffin committed Jul 12, 2019
1 parent cbff896 commit 7f024e5e7b6593e8e9f8d8768612b87016386a43
Showing with 206 additions and 74 deletions.
  1. +6 −1 src/Server/Coderr.Server.Api/Core/Incidents/Queries/FindIncidentsResultItem.cs
  2. +17 −1 src/Server/Coderr.Server.ReportAnalyzer/Inbound/SaveReportHandler.cs
  3. +4 −1 src/Server/Coderr.Server.SqlServer/Core/Incidents/Queries/FindIncidentResultItemMapper.cs
  4. +0 −1 src/Server/Coderr.Server.Web/ClientApp/boot.ts
  5. +3 −2 src/Server/Coderr.Server.Web/ClientApp/components/analyze/incidents/incident.ts
  6. +1 −1 src/Server/Coderr.Server.Web/ClientApp/components/analyze/incidents/incident.vue.html
  7. +5 −1 src/Server/Coderr.Server.Web/ClientApp/components/analyze/menu.css
  8. +27 −1 src/Server/Coderr.Server.Web/ClientApp/components/analyze/menu.ts
  9. +58 −33 src/Server/Coderr.Server.Web/ClientApp/components/analyze/menu.vue.html
  10. +38 −6 src/Server/Coderr.Server.Web/ClientApp/components/analyze/myincidents.ts
  11. +1 −1 src/Server/Coderr.Server.Web/ClientApp/components/discover/incidents/incident.vue.html
  12. +1 −1 src/Server/Coderr.Server.Web/ClientApp/components/discover/incidents/search.ts
  13. +3 −0 src/Server/Coderr.Server.Web/ClientApp/components/home/navmenu/navmenu.ts
  14. +0 −1 src/Server/Coderr.Server.Web/ClientApp/components/shared/incidents/ContextNavigator.ts
  15. +1 −0 src/Server/Coderr.Server.Web/ClientApp/dto/Core/Accounts.ts
  16. +2 −0 src/Server/Coderr.Server.Web/ClientApp/dto/Core/ApiKeys.ts
  17. +4 −1 src/Server/Coderr.Server.Web/ClientApp/dto/Core/Incidents.ts
  18. +1 −0 src/Server/Coderr.Server.Web/ClientApp/services/AppRoot.ts
  19. +1 −0 src/Server/Coderr.Server.Web/ClientApp/services/applications/ApplicationService.ts
  20. +3 −3 src/Server/Coderr.Server.Web/ClientApp/services/incidents/IncidentService.ts
  21. +2 −2 src/Server/Coderr.Server.Web/ClientApp/vue-shim.d.ts
  22. +7 −0 src/Server/Coderr.Server.Web/Controllers/ReportReceiverController.cs
  23. +14 −13 src/Server/Coderr.Server.Web/Startup.cs
  24. +1 −1 src/Server/Coderr.Server.Web/appsettings.json
  25. +6 −3 src/Server/Coderr.Server.Web/tsconfig.json
@@ -31,7 +31,7 @@ protected FindIncidentsResultItem()
/// <summary>
/// Id of the application that this incident belongs to
/// </summary>
public string ApplicationId { get; set; }
public int ApplicationId { get; set; }

/// <summary>
/// Name of the application that this incident belongs to
@@ -43,6 +43,11 @@ protected FindIncidentsResultItem()
/// </summary>
public DateTime CreatedAtUtc { get; set; }

/// <summary>
/// When the incident was assigned to someone.
/// </summary>
public DateTime? AssignedAtUtc { get; set; }

/// <summary>
/// Incident id
/// </summary>
@@ -64,7 +64,8 @@ public void AddFilter(Func<NewReportDTO, bool> filter)
throw new InvalidCredentialException($"AppKey was not found in the database. Key '{appKey}'.");
}

if (!ReportValidator.ValidateBody(application.SharedSecret, signatureProvidedByTheClient, reportBody))
// web(js) applications do not sign the body
if (signatureProvidedByTheClient != null && !ReportValidator.ValidateBody(application.SharedSecret, signatureProvidedByTheClient, reportBody))
{
await StoreInvalidReportAsync(appKey, signatureProvidedByTheClient, remoteAddress, reportBody);
throw new AuthenticationException(
@@ -158,6 +159,21 @@ private NewReportDTO DeserializeBody(byte[] body)

if (string.IsNullOrEmpty(dto.EnvironmentName) && !string.IsNullOrEmpty(dto.Environment))
dto.EnvironmentName = dto.Environment;

// Safeguard against malformed reports (other clients than the built in ones)
if (dto.Exception == null)
return null;
if (string.IsNullOrWhiteSpace(dto.Exception.Name) && string.IsNullOrWhiteSpace(dto.Exception.FullName))
return null;
if (string.IsNullOrWhiteSpace(dto.Exception.Name))
dto.Exception.Name = dto.Exception.FullName;
if (string.IsNullOrWhiteSpace(dto.Exception.FullName))
dto.Exception.FullName = dto.Exception.Name;
if (dto.Exception.BaseClasses == null)
dto.Exception.BaseClasses = new string[0];
if (dto.Exception.Namespace == null)
dto.Exception.Namespace = "";

return dto;
}

@@ -20,7 +20,7 @@ public void Map(IDataRecord source, object destination)
public void Map(IDataRecord source, FindIncidentsResultItem destination)
{
destination.ApplicationName = (string) source["ApplicationName"];
destination.ApplicationId = source["ApplicationId"].ToString();
destination.ApplicationId = (int)source["ApplicationId"];
destination.IsReOpened = source["IsReopened"].Equals(1);
destination.ReportCount = (int) source["ReportCount"];
destination.CreatedAtUtc = (DateTime)source["CreatedAtUtc"];
@@ -31,6 +31,9 @@ public void Map(IDataRecord source, FindIncidentsResultItem destination)

value = source["LastReportAtUtc"];
destination.LastReportReceivedAtUtc = (DateTime) (value is DBNull ? destination.LastUpdateAtUtc : value);

value = source["AssignedAtUtc"];
destination.AssignedAtUtc = (DateTime?)(value is DBNull ? null : value);
}
}
}
@@ -3,7 +3,6 @@ import Vue from "vue";
import VueRouter from "vue-router";
import moment from "moment";
import { AppRoot } from "./services/AppRoot"
//import VeeValidate from 'vee-validate';
import { IUser } from "./vue-shim";

//Vue.use(VeeValidate);
@@ -27,11 +27,12 @@ export default class AnalyzeIncidentComponent extends Vue {
currentCollectionName: string = '';

created() {
// required for contextnavigator
this.incidentId = parseInt(this.$route.params.incidentId, 10);
}

mounted() {
var incidentId = parseInt(this.$route.params.incidentId, 10);
this.loadIncident(incidentId);
this.loadIncident(this.incidentId);
MyIncidents.Instance.subscribeOnSelectedIncident(this.selectIncident);
}

@@ -69,7 +69,7 @@ <h3>Quick facts</h3>
<div class="col">
<div class="card">
<div class="card-body">
<context-navigator :incidentId="incident.Id" :showAnalyzeFooter="true" />
<context-navigator :incidentId="incidentId" :showAnalyzeFooter="true" />
</div>
</div>
</div>
@@ -10,4 +10,8 @@
.bg-light .router-link-exact-active {
color: #333333;
font-weight: bold;
}*/
}*/
td {
padding-right: 15px;
padding-top: 5px;
}
@@ -1,5 +1,7 @@
import { PubSubService, MessageContext } from "../../services/PubSub";
import * as MenuApi from "../../services/menu/MenuApi";
import Vue from 'vue';
import Vue from "vue";
import { AppRoot } from '../../services/AppRoot';
import { Component, Watch } from 'vue-property-decorator';
import { MyIncidents, IMyIncident } from "./myincidents";

@@ -18,11 +20,15 @@ export default class AnalyzeMenuComponent extends Vue {
incidents: IMyIncident[] = [];
title = '';
incidentId: number | null = null;
toggleMenu = false;
applicationId: number | null = null;

created() {
if (this.$route.params.incidentId) {
this.incidentId = parseInt(this.$route.params.incidentId, 10);
}
this.applicationId = AppRoot.Instance.currentApplicationId;
PubSubService.Instance.subscribe(MenuApi.MessagingTopics.ApplicationChanged, this.onApplicationChangedInNavMenu);
MyIncidents.Instance.subscribeOnSelectedIncident(this.onIncidentSelected);
MyIncidents.Instance.subscribeOnListChanges(this.onListChanged);
}
@@ -41,6 +47,12 @@ export default class AnalyzeMenuComponent extends Vue {
destroyed() {
MyIncidents.Instance.unsubscribe(this.onIncidentSelected);
MyIncidents.Instance.unsubscribe(this.onListChanged);
PubSubService.Instance.unsubscribe(MenuApi.MessagingTopics.ApplicationChanged, this.onApplicationChangedInNavMenu);
}

toggleIncidentMenu() {
this.toggleMenu = !this.toggleMenu;

}

private onListChanged(args: any) {
@@ -62,6 +74,20 @@ export default class AnalyzeMenuComponent extends Vue {
}
}

@Watch('$route.params.applicationId')
onAppRoute(value: string, oldValue: string) {
if (!value) {
this.applicationId = null;
return;
} else {
this.applicationId = parseInt(value, 10);
}
}
private onApplicationChangedInNavMenu(ctx: MessageContext) {
var body = <MenuApi.ApplicationChanged>ctx.message.body;
this.applicationId = body.applicationId;
}

@Watch('$route.params.incidentId')
onIncidentRoute(value: string, oldValue: string) {
if (this.$route.fullPath.indexOf('/analyze/') === -1) {
@@ -1,39 +1,64 @@
<template>
<div class="mb-3 analyze-menu submenu" v-if="incidentId">
<div class="navbar-light navbar bg-light">
<ul class="nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="myIncidentsBtn" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{title}}
</a>
<div class="dropdown-menu" aria-labelledby="myIncidentsBtn">
<h6 class="dropdown-header">Select one of your incidents</h6>
<router-link class="dropdown-item" :to="{ name: 'analyzeIncident', params: {incidentId: item.incidentId.toString() }}" v-for="item in incidents" :key="item.incidentId">
<div class="mb-3">
<div class="mb-0 analyze-menu submenu" v-if="incidentId">
<div class="navbar-light navbar bg-light">
<ul class="nav">
<li class="nav-item dropdown">
<a href="#" @click.prevent="toggleIncidentMenu" class="nav-link" id="myIncidentsBtn">
<i class="fa fa-bars"></i>
</a>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'analyzeIncident', params: {incidentId: incidentId }}" exact>
<span class="fa fa-chart-line"></span> Overview
</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'analyzeOrigins', params: {incidentId: incidentId }}">
<span class="fa fa-globe"></span> Error origins
</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'analyzeReport', params: {incidentId: incidentId }}">
<span class="fa fa-table"></span> Analyze reports
</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'analyzeFeedback', params: {incidentId: incidentId }}">
<span class="fa fa-comment"></span> Bug reports
</router-link>
</li>
</ul>
</div>
</div>
<div v-show="toggleMenu" class="bg-dark text-blue mt-0 p-5">
<h3 class="text-blue">My incidents</h3>
<table>
<tr>
<th>Name</th>
<th v-if="applicationId == null">Application</th>
<th>Created</th>
<th>Assigned since</th>
</tr>
<tr v-for="item in incidents">
<td class="text-left">
<router-link :to="{ name: 'analyzeIncident', params: {incidentId: item.incidentId.toString() }}" :key="item.incidentId">
{{item.title}}
</router-link>
</div>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'analyzeIncident', params: {incidentId: incidentId }}" exact>
<span class="fa fa-chart-line"></span> Overview
</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'analyzeOrigins', params: {incidentId: incidentId }}">
<span class="fa fa-globe"></span> Error origins
</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'analyzeReport', params: {incidentId: incidentId }}">
<span class="fa fa-table"></span> Analyze reports
</router-link>
</li>
<li class="nav-item">
<router-link class="nav-link" :to="{name: 'analyzeFeedback', params: {incidentId: incidentId }}">
<span class="fa fa-comment"></span> Bug reports
</router-link>
</li>
</ul>
</td>
<td v-if="!applicationId">
{{item.applicationName}}
</td>
<td>
{{item.createdAtUtc|ago}}
</td>
<td>
{{item.assignedAtUtc|ago}}
</td>
</tr>

</table>

</div>
</div>
</template>
@@ -2,6 +2,7 @@ import * as MenuApi from "../../services/menu/MenuApi";
import { AppRoot } from "../../services/AppRoot";
import { PubSubService, MessageContext } from "../../services/PubSub";
import { IncidentTopcis, IncidentAssigned, IncidentClosed, IncidentIgnored } from "../../services/incidents/IncidentService";
import * as incidents from "../../dto/Core/Incidents";

/**
* null if the user do not have any assigned incidents (for the selected application)
@@ -21,6 +22,9 @@ export interface incidentListChanged {
export interface IMyIncident {
incidentId: number;
applicationId: number;
applicationName: string;
createdAtUtc: Date;
assignedAtUtc: Date;
title: string;

/**
@@ -142,9 +146,16 @@ export class MyIncidents {
var index = this.allMyIncidents$.findIndex(menuItem => menuItem.incidentId === assignedIncident.Id);
var item: IMyIncident;
if (index === -1) {
item = this.createItem(assignedIncident.Id,
item = this.createItem(
assignedIncident.Id,
assignedIncident.ApplicationId,
assignedIncident.Description);
'',
assignedIncident.CreatedAtUtc,
assignedIncident.AssignedAtUtc,
assignedIncident.Description
);
AppRoot.Instance.applicationService.get(assignedIncident.ApplicationId)
.then(x => item.applicationName = x.name);
this.allMyIncidents$.push(item);
} else {
item = this.allMyIncidents$[index];
@@ -182,7 +193,7 @@ export class MyIncidents {
}
mine.forEach(dto => {
if (!this.allMyIncidents$.find(item => item.incidentId === dto.Id)) {
var item = this.createItem(dto.Id, parseInt(dto.ApplicationId, 10), dto.Name);
var item = this.createItem2(dto);
this.allMyIncidents$.push(item);
}
});
@@ -200,7 +211,7 @@ export class MyIncidents {
});
}

private createItem(incidentId: number, applicationId: number, title: string): IMyIncident {
private createItem(incidentId: number, applicationId: number, applicationName: string, createdAtUtc: Date, assignedAtUtc: Date, title: string): IMyIncident {
let shortTitle = title;
if (shortTitle.length > 50) {
shortTitle = title.substr(0, 45) + '[...]';
@@ -210,7 +221,28 @@ export class MyIncidents {
title: title,
shortTitle: shortTitle,
incidentId: incidentId,
applicationId: applicationId
createdAtUtc: createdAtUtc,
applicationId: applicationId,
applicationName: applicationName,
assignedAtUtc: assignedAtUtc
};
return item;
}

private createItem2(incident: incidents.FindIncidentsResultItem): IMyIncident {
let shortTitle = incident.Name;
if (shortTitle.length > 50) {
shortTitle = incident.Name.substr(0, 45) + '[...]';
}

var item: IMyIncident = {
title: incident.Name,
shortTitle: shortTitle,
incidentId: incident.Id,
createdAtUtc: incident.CreatedAtUtc,
applicationId: incident.ApplicationId,
applicationName: incident.ApplicationName,
assignedAtUtc: incident.AssignedAtUtc
};
return item;
}
@@ -227,7 +259,7 @@ export class MyIncidents {
var foundItem: IMyIncident = null;
allMine.forEach(myIncident => {
if (myIncident.Id === incidentId) {
var item = this.createItem(myIncident.Id, parseInt(myIncident.ApplicationId, 10), myIncident.Name);
var item = this.createItem2(myIncident);
this.allMyIncidents$.push(item);
foundItem = item;
}
@@ -55,7 +55,7 @@ <h3 class="">Report history</h3>
<div class="col">
<div class="card">
<div class="card-body">
<context-navigator :incidentId="incident.Id" />
<context-navigator :incidentId="incidentId" />
</div>
</div>
</div>
@@ -289,7 +289,7 @@ export default class IncidentSearchComponent extends Vue {
this.incidents.splice(0);
result.Items.forEach(item => {
var entity: Incident = {
ApplicationId: parseInt(item.ApplicationId, 10),
ApplicationId: item.ApplicationId,
ApplicationName: item.ApplicationName,
CreatedAtUtc: item.CreatedAtUtc,
Id: item.Id,

0 comments on commit 7f024e5

Please sign in to comment.
You can’t perform that action at this time.