Merge branch 'multiple-skill-installation' of github.com:MycroftAI/selene into multiple-skill-installation

pull/9/head
Matheus Lima 2018-10-26 14:51:02 -03:00
commit 8dad2343a7
7 changed files with 149 additions and 40 deletions

View File

@ -14,7 +14,7 @@ class LogoutEndpoint(SeleneEndpoint):
def __init__(self):
super(LogoutEndpoint, self).__init__()
def put(self):
def get(self):
try:
self._authenticate()
self._logout()

View File

@ -81,6 +81,9 @@ class AvailableSkillsEndpoint(SeleneEndpoint):
def _reformat_skills(self, skills_to_include: List[RepositorySkill]):
"""Build the response data from the skill service response"""
for skill in skills_to_include:
trigger = None
if skill.triggers:
trigger = skill.triggers[0]
skill_info = dict(
icon=skill.icon,
iconImage=skill.icon_image,
@ -90,7 +93,7 @@ class AvailableSkillsEndpoint(SeleneEndpoint):
name=skill.skill_name,
summary=skill.summary,
title=skill.title,
trigger=skill.triggers[0]
trigger=trigger
)
self.response_skills.append(skill_info)

View File

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { Observable, Subject } from "rxjs";
import { AvailableSkill, SkillDetail } from "./skills.service";
// Status values that can be expected in the install status endpoint response.
type InstallStatus = 'failed' | 'installed' | 'installing' | 'uninstalling';
export interface SkillInstallStatus {
@ -18,11 +19,9 @@ export interface Installations {
installStatuses: SkillInstallStatus;
}
// const inProgressStatuses = ['installing', 'uninstalling', 'failed'];
const inProgressStatuses = ['installing', 'uninstalling'];
const inProgressStatuses = ['installing', 'uninstalling', 'failed'];
const installStatusUrl = '/api/skill/installations';
const installUrl = '/api/skill/install';
const uninstallUrl = '/api/skill/uninstall';
const installerSettingsUrl = '/api/skill/install';
@Injectable({
providedIn: 'root'
@ -32,7 +31,7 @@ export class InstallService {
public installStatuses = new Subject<SkillInstallStatus>();
public newInstallStatuses: SkillInstallStatus;
private prevInstallStatuses: SkillInstallStatus;
public statusNotifications = new Subject<SkillInstallStatus>();
public statusNotifications = new Subject<string[]>();
constructor(private http: HttpClient) { }
@ -52,7 +51,7 @@ export class InstallService {
applyInstallStatusChanges() {
if (this.prevInstallStatuses) {
Object.keys(this.newInstallStatuses).forEach(
() => this.compareStatuses
(skillName) => {this.compareStatuses(skillName);}
);
}
this.prevInstallStatuses = this.newInstallStatuses;
@ -77,39 +76,41 @@ export class InstallService {
compareStatuses(skillName: string) {
let prevSkillStatus = this.prevInstallStatuses[skillName];
let newSkillStatus = this.newInstallStatuses[skillName];
let statusNotifications: SkillInstallStatus = {};
switch (prevSkillStatus) {
case ('installing'): {
if (['installed', 'failed'].includes(newSkillStatus)) {
statusNotifications[skillName] = newSkillStatus;
if (newSkillStatus === 'installed') {
this.statusNotifications.next([skillName, newSkillStatus]);
this.removeFromInstallQueue(skillName);
} else if (newSkillStatus === 'failed') {
this.statusNotifications.next([skillName, 'install failed']);
} else {
this.newInstallStatuses[skillName] = prevSkillStatus;
}
break;
}
case ('uninstalling'): {
if (!newSkillStatus || newSkillStatus === 'failed') {
statusNotifications[skillName] = newSkillStatus;
if (!newSkillStatus) {
this.statusNotifications.next([skillName, 'uninstalled']);
this.removeFromUninstallQueue(skillName);
} else if (newSkillStatus === 'failed') {
this.statusNotifications.next([skillName, 'uninstall failed']);
} else {
this.newInstallStatuses[skillName] = prevSkillStatus;
}
break;
}
case ('failed'): {
if (!newSkillStatus || newSkillStatus != 'installed') {
statusNotifications[skillName] = newSkillStatus;
if (!newSkillStatus) {
this.statusNotifications.next([skillName, 'uninstalled']);
} else if (newSkillStatus != 'installed') {
this.statusNotifications.next([skillName, newSkillStatus]);
} else {
this.newInstallStatuses[skillName] = prevSkillStatus;
}
break;
}
}
if (statusNotifications) {
this.statusNotifications.next(statusNotifications)
}
}
/***
@ -142,7 +143,7 @@ export class InstallService {
* the result of a requested install/uninstall.
*/
checkInstallationsInProgress() {
let inProgress = Object.values(this.installStatuses).filter(
let inProgress = Object.values(this.newInstallStatuses).filter(
(installStatus) => inProgressStatuses.includes(installStatus)
);
if (inProgress.length > 0) {
@ -151,26 +152,66 @@ export class InstallService {
}
/**
* Call the API endpoint for installing a skill.
* Call the API to add a skill to the Installer skill's "to_install" setting.
*
* @param skill: the skill being installed
* @param skillName: the skill being installed
*/
installSkill(skill: AvailableSkill): Observable<Object> {
addToInstallQueue(skillName: string): Observable<Object> {
return this.http.put<Object>(
installUrl,
{skill_name: skill.name}
installerSettingsUrl,
{
action: "add",
section: "to_install",
skill_name: skillName
}
)
}
/**
* Call the API endpoint for uninstalling a skill.
* Call the API to add a skill to the Installer skill's "to_remove" setting.
*
* @param skill: the skill being removed
* @param skillName: the skill being removed
*/
uninstallSkill(skill: AvailableSkill): Observable<Object> {
addToUninstallQueue(skillName: string): Observable<Object> {
return this.http.put<Object>(
uninstallUrl,
{skill_name: skill.name}
installerSettingsUrl,
{
action: "add",
section: "to_remove",
skill_name: skillName
}
)
}
/**
* Call the API to remove a skill to the Installer skill's "to_install" setting.
*
* @param skillName: the skill being installed
*/
removeFromInstallQueue(skillName: string): Observable<Object> {
return this.http.put<Object>(
installerSettingsUrl,
{
action: "remove",
section: "to_install",
skill_name: skillName
}
)
}
/**
* Call the API to remove a skill to the Installer skill's "to_remove" setting.
*
* @param skillName: the skill being removed
*/
removeFromUninstallQueue(skillName: string): Observable<Object> {
return this.http.put<Object>(
installerSettingsUrl,
{
action: "remove",
section: "to_remove",
skill_name: skillName
}
)
}
}

View File

@ -11,7 +11,7 @@ import { faLock } from "@fortawesome/free-solid-svg-icons/faLock";
import { MatSnackBar } from "@angular/material";
const fiveSeconds = 5000;
const twentySeconds = 20000;
const tenSeconds = 10000;
@Component({
@ -59,7 +59,7 @@ export class InstallButtonComponent implements OnInit {
* Install a skill onto one or many devices
*/
install_skill() : void {
this.installService.installSkill(this.skill).subscribe(
this.installService.addToInstallQueue(this.skill.name).subscribe(
(response) => {
this.onInstallSuccess(response)
},
@ -86,7 +86,7 @@ export class InstallButtonComponent implements OnInit {
'to your devices. Please allow up to two minutes for ' +
'installation to complete before using the skill.',
null,
{panelClass: 'mycroft-snackbar', duration: twentySeconds}
{panelClass: 'mycroft-snackbar', duration: tenSeconds}
);
}
@ -112,7 +112,7 @@ export class InstallButtonComponent implements OnInit {
* Remove a skill from one or many devices
*/
uninstallSkill() : void {
this.installService.uninstallSkill(this.skill).subscribe(
this.installService.addToUninstallQueue(this.skill.name).subscribe(
(response) => {
this.onUninstallSuccess(response)
},
@ -136,7 +136,7 @@ export class InstallButtonComponent implements OnInit {
'uninstalling. Please allow up to a minute for the skill to be ' +
'removed from devices.',
null,
{panelClass: 'mycroft-snackbar', duration: twentySeconds}
{panelClass: 'mycroft-snackbar', duration: tenSeconds}
);
}
}

View File

@ -38,3 +38,7 @@ mat-card {
mat-card:hover{
box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.2);
}
.login-snackbar {
text-align: center;
}

View File

@ -2,18 +2,77 @@
* Format the header portion of a skill summary card. This includes the icon
* for the skill and a Mycroft logo if the skill is authored by Mycroft AI.
*/
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit} from '@angular/core';
import { MatSnackBar } from "@angular/material";
import { AvailableSkill } from "../../skills.service";
import { InstallService } from "../../install.service";
import { faComment } from "@fortawesome/free-solid-svg-icons";
const fiveSeconds = 5000;
@Component({
selector: 'market-skill-card',
templateUrl: './skill-card.component.html',
styleUrls: ['./skill-card.component.scss']
})
export class SkillCardComponent {
export class SkillCardComponent implements OnInit {
@Input() public skill: AvailableSkill;
public voiceIcon = faComment;
constructor() { }
}
constructor(
public installSnackbar: MatSnackBar,
private installService: InstallService) {
}
ngOnInit() {
this.installService.statusNotifications.subscribe(
(statusChange) => {
this.showStatusNotifications(statusChange);
}
);
}
showStatusNotifications(statusChange: string[]) {
let notificationMessage: string;
let [skillName, notificationStatus] = statusChange;
if (this.skill.name === skillName) {
switch (notificationStatus) {
case ('installed'): {
notificationMessage = 'The ' + this.skill.title + ' skill has ' +
'been added to all your devices.';
this.showInstallStatusNotification(notificationMessage);
break;
}
case ('uninstalled'): {
notificationMessage = 'The ' + this.skill.title + ' skill has ' +
'been removed from all your devices.';
this.showInstallStatusNotification(notificationMessage);
break;
}
case ('install failed'): {
notificationMessage = 'The ' + this.skill.title + ' failed to ' +
'install to one or more of your devices. Install will be ' +
'retried until successful';
this.showInstallStatusNotification(notificationMessage);
break;
}
case ('uninstall failed'): {
notificationMessage = 'The ' + this.skill.title + ' failed to ' +
'uninstall from one or more of your devices. Uninstall ' +
'will be retried until successful';
this.showInstallStatusNotification(notificationMessage)
}
}
}
}
showInstallStatusNotification(notificationMessage: string) {
this.installSnackbar.open(
notificationMessage,
'',
{panelClass: 'login-snackbar', duration: fiveSeconds}
)
}
}

View File

@ -40,6 +40,8 @@ export class SkillSearchComponent implements OnInit, OnDestroy {
searchSkills(): void {
this.skillsService.searchSkills(this.searchTerm).subscribe(
(skills) => {
this.skillsService.availableSkills = skills;
this.skillsService.getSkillCategories();
this.searchResults.emit(skills);
}
);