Merge branch 'multiple-skill-installation' of github.com:MycroftAI/selene into multiple-skill-installation
commit
8dad2343a7
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue