changed skill install status logic to be its own endpoint
parent
711ef460f9
commit
42c510665e
|
@ -3,9 +3,10 @@ from flask_restful import Api
|
||||||
|
|
||||||
from .config import get_config_location
|
from .config import get_config_location
|
||||||
from market_api.endpoints import (
|
from market_api.endpoints import (
|
||||||
SkillSummaryEndpoint,
|
AvailableSkillsEndpoint,
|
||||||
SkillDetailEndpoint,
|
SkillDetailEndpoint,
|
||||||
SkillInstallEndpoint,
|
SkillInstallEndpoint,
|
||||||
|
SkillInstallationsEndpoint,
|
||||||
UserEndpoint
|
UserEndpoint
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,7 +15,14 @@ marketplace = Flask(__name__)
|
||||||
marketplace.config.from_object(get_config_location())
|
marketplace.config.from_object(get_config_location())
|
||||||
|
|
||||||
marketplace_api = Api(marketplace)
|
marketplace_api = Api(marketplace)
|
||||||
marketplace_api.add_resource(SkillSummaryEndpoint, '/api/skills')
|
marketplace_api.add_resource(AvailableSkillsEndpoint, '/api/skill/available')
|
||||||
marketplace_api.add_resource(SkillDetailEndpoint, '/api/skill/<skill_id>')
|
marketplace_api.add_resource(
|
||||||
marketplace_api.add_resource(SkillInstallEndpoint, '/api/install')
|
SkillDetailEndpoint,
|
||||||
|
'/api/skill/detail/<skill_name>'
|
||||||
|
)
|
||||||
|
marketplace_api.add_resource(SkillInstallEndpoint, '/api/skill/install')
|
||||||
|
marketplace_api.add_resource(
|
||||||
|
SkillInstallationsEndpoint,
|
||||||
|
'/api/skill/installations'
|
||||||
|
)
|
||||||
marketplace_api.add_resource(UserEndpoint, '/api/user')
|
marketplace_api.add_resource(UserEndpoint, '/api/user')
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
|
from .available_skills import AvailableSkillsEndpoint
|
||||||
from .skill_detail import SkillDetailEndpoint
|
from .skill_detail import SkillDetailEndpoint
|
||||||
from .skill_install import SkillInstallEndpoint
|
from .skill_install import SkillInstallEndpoint
|
||||||
from .skill_summary import SkillSummaryEndpoint
|
from .skill_install_status import SkillInstallationsEndpoint
|
||||||
from .user import UserEndpoint
|
from .user import UserEndpoint
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
"""Endpoint to provide skill summary data to the marketplace."""
|
||||||
|
from collections import defaultdict
|
||||||
|
from http import HTTPStatus
|
||||||
|
from logging import getLogger
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import requests as service_request
|
||||||
|
|
||||||
|
from selene_util.api import APIError, SeleneEndpoint
|
||||||
|
from .common import RepositorySkill
|
||||||
|
|
||||||
|
_log = getLogger(__package__)
|
||||||
|
|
||||||
|
|
||||||
|
class AvailableSkillsEndpoint(SeleneEndpoint):
|
||||||
|
authentication_required = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(AvailableSkillsEndpoint, self).__init__()
|
||||||
|
self.available_skills: List[RepositorySkill] = []
|
||||||
|
self.response_skills: List[dict] = []
|
||||||
|
self.skills_in_manifests = defaultdict(list)
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
try:
|
||||||
|
self._get_available_skills()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self._build_response_data()
|
||||||
|
self.response = (self.response_skills, HTTPStatus.OK)
|
||||||
|
|
||||||
|
return self.response
|
||||||
|
|
||||||
|
def _get_available_skills(self):
|
||||||
|
"""Retrieve all skills in the skill repository.
|
||||||
|
|
||||||
|
The data is retrieved from a database table that is populated with
|
||||||
|
the contents of a JSON object in the mycroft-skills-data Github
|
||||||
|
repository. The JSON object contains metadata about each skill.
|
||||||
|
"""
|
||||||
|
skill_service_response = service_request.get(
|
||||||
|
self.config['SELENE_BASE_URL'] + '/skill/all'
|
||||||
|
)
|
||||||
|
if skill_service_response.status_code != HTTPStatus.OK:
|
||||||
|
self._check_for_service_errors(skill_service_response)
|
||||||
|
self.available_skills = [
|
||||||
|
RepositorySkill(**skill) for skill in skill_service_response.json()
|
||||||
|
]
|
||||||
|
|
||||||
|
def _build_response_data(self):
|
||||||
|
"""Build the data to include in the response."""
|
||||||
|
if self.request.query_string:
|
||||||
|
skills_to_include = self._filter_skills()
|
||||||
|
else:
|
||||||
|
skills_to_include = self.available_skills
|
||||||
|
self._reformat_skills(skills_to_include)
|
||||||
|
self._sort_skills()
|
||||||
|
|
||||||
|
def _filter_skills(self) -> list:
|
||||||
|
"""If search criteria exist, only return those skills that match."""
|
||||||
|
skills_to_include = []
|
||||||
|
|
||||||
|
query_string = self.request.query_string.decode()
|
||||||
|
search_term = query_string.lower().split('=')[1]
|
||||||
|
for skill in self.available_skills:
|
||||||
|
search_term_match = (
|
||||||
|
search_term is None or
|
||||||
|
search_term in skill.title.lower() or
|
||||||
|
search_term in skill.description.lower() or
|
||||||
|
search_term in skill.summary.lower() or
|
||||||
|
search_term in [c.lower() for c in skill.categories] or
|
||||||
|
search_term in [t.lower() for t in skill.tags] or
|
||||||
|
search_term in [t.lower() for t in skill.triggers]
|
||||||
|
)
|
||||||
|
if search_term_match:
|
||||||
|
skills_to_include.append(skill)
|
||||||
|
|
||||||
|
return skills_to_include
|
||||||
|
|
||||||
|
def _reformat_skills(self, skills_to_include: List[RepositorySkill]):
|
||||||
|
"""Build the response data from the skill service response"""
|
||||||
|
for skill in skills_to_include:
|
||||||
|
skill_info = dict(
|
||||||
|
icon=skill.icon,
|
||||||
|
iconImage=skill.icon_image,
|
||||||
|
isMycroftMade=skill.is_mycroft_made,
|
||||||
|
isSystemSkill=skill.is_system_skill,
|
||||||
|
marketCategory=skill.market_category,
|
||||||
|
name=skill.skill_name,
|
||||||
|
summary=skill.summary,
|
||||||
|
title=skill.title,
|
||||||
|
trigger=skill.triggers[0]
|
||||||
|
)
|
||||||
|
self.response_skills.append(skill_info)
|
||||||
|
|
||||||
|
def _sort_skills(self):
|
||||||
|
"""Sort the skills in alphabetical order"""
|
||||||
|
sorted_skills = sorted(
|
||||||
|
self.response_skills,
|
||||||
|
key=lambda skill:
|
||||||
|
skill['title']
|
||||||
|
)
|
||||||
|
self.response_skills = sorted_skills
|
|
@ -1,12 +1,62 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass, field
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from markdown import markdown
|
||||||
|
|
||||||
import requests as service_request
|
import requests as service_request
|
||||||
|
|
||||||
|
DEFAULT_ICON_COLOR = '#6C7A89'
|
||||||
|
DEFAULT_ICON_NAME = 'comment-alt'
|
||||||
|
SYSTEM_CATEGORY = 'System'
|
||||||
|
UNDEFINED_CATEGORY = 'Not Categorized'
|
||||||
VALID_INSTALLATION_VALUES = ('failed', 'installed', 'installing', 'uninstalled')
|
VALID_INSTALLATION_VALUES = ('failed', 'installed', 'installing', 'uninstalled')
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RepositorySkill(object):
|
||||||
|
"""Represents a single skill defined in the Mycroft Skills repository."""
|
||||||
|
branch: str
|
||||||
|
categories: List[str]
|
||||||
|
created: str
|
||||||
|
credits: List[dict]
|
||||||
|
description: str
|
||||||
|
icon: dict
|
||||||
|
id: str
|
||||||
|
is_mycroft_made: bool = field(init=False)
|
||||||
|
is_system_skill: bool = field(init=False)
|
||||||
|
last_update: str
|
||||||
|
market_category: str = field(init=False)
|
||||||
|
platforms: List[str]
|
||||||
|
repository_owner: str
|
||||||
|
repository_url: str
|
||||||
|
skill_name: str
|
||||||
|
summary: str
|
||||||
|
tags: List[str]
|
||||||
|
title: str
|
||||||
|
triggers: List[str]
|
||||||
|
icon_image: str = field(default=None)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.is_system_skill = False
|
||||||
|
if 'system' in self.tags:
|
||||||
|
self.is_system_skill = True
|
||||||
|
self.market_category = SYSTEM_CATEGORY
|
||||||
|
elif self.categories:
|
||||||
|
# a skill may have many categories. the first one in the
|
||||||
|
# list is considered the "primary" category. This is the
|
||||||
|
# category the marketplace will use to group the skill.
|
||||||
|
self.market_category = self.categories[0]
|
||||||
|
else:
|
||||||
|
self.market_category = UNDEFINED_CATEGORY
|
||||||
|
|
||||||
|
if not self.icon:
|
||||||
|
self.icon = dict(icon=DEFAULT_ICON_NAME, color=DEFAULT_ICON_COLOR)
|
||||||
|
|
||||||
|
self.is_mycroft_made = self.credits[0].get('name') == 'Mycroft AI'
|
||||||
|
self.summary = markdown(self.summary, output_format='html5')
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ManifestSkill(object):
|
class ManifestSkill(object):
|
||||||
"""Represents a single skill on a device's skill manifest.
|
"""Represents a single skill on a device's skill manifest.
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import requests as service_request
|
||||||
|
|
||||||
|
from selene_util.api import APIError, SeleneEndpoint
|
||||||
|
|
||||||
|
VALID_INSTALLATION_VALUES = (
|
||||||
|
'failed',
|
||||||
|
'installed',
|
||||||
|
'installing',
|
||||||
|
'uninstalling'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ManifestSkill(object):
|
||||||
|
"""Represents a single skill on a device's skill manifest.
|
||||||
|
|
||||||
|
Mycroft core keeps a manifest off all skills associated with a device.
|
||||||
|
This manifest shows the status of each skill as it relates to the device.
|
||||||
|
"""
|
||||||
|
failure_message: str
|
||||||
|
installation: str
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class SkillInstallationsEndpoint(SeleneEndpoint):
|
||||||
|
authentication_required = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(SkillInstallationsEndpoint, self).__init__()
|
||||||
|
self.skills_in_manifests = defaultdict(list)
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
try:
|
||||||
|
self._get_install_statuses()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
response_data = self._build_response_data()
|
||||||
|
self.response = (response_data, HTTPStatus.OK)
|
||||||
|
|
||||||
|
return self.response
|
||||||
|
|
||||||
|
def _get_install_statuses(self):
|
||||||
|
self._authenticate()
|
||||||
|
if self.authenticated:
|
||||||
|
skill_manifests = self._get_skill_manifests()
|
||||||
|
self._parse_skill_manifests(skill_manifests)
|
||||||
|
else:
|
||||||
|
self.response = (
|
||||||
|
dict(installStatuses=[], failureReasons=[]),
|
||||||
|
HTTPStatus.OK
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_skill_manifests(self) -> dict:
|
||||||
|
"""Get the skill manifests from each of a user's devices
|
||||||
|
|
||||||
|
The skill manifests will be used to determine the status of each
|
||||||
|
skill as it relates to the marketplace.
|
||||||
|
"""
|
||||||
|
service_request_headers = {
|
||||||
|
'Authorization': 'Bearer ' + self.tartarus_token
|
||||||
|
}
|
||||||
|
service_url = (
|
||||||
|
self.config['TARTARUS_BASE_URL'] +
|
||||||
|
'/user/' +
|
||||||
|
self.user_uuid +
|
||||||
|
'/skillJson'
|
||||||
|
)
|
||||||
|
response = service_request.get(
|
||||||
|
service_url,
|
||||||
|
headers=service_request_headers
|
||||||
|
)
|
||||||
|
|
||||||
|
self._check_for_service_errors(response)
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def _parse_skill_manifests(self, skill_manifests: dict):
|
||||||
|
for device in skill_manifests.get('devices', []):
|
||||||
|
for skill in device['skills']:
|
||||||
|
manifest_skill = ManifestSkill(
|
||||||
|
failure_message=skill['failure_message'],
|
||||||
|
installation=skill['installation'],
|
||||||
|
name=skill['name']
|
||||||
|
)
|
||||||
|
self.skills_in_manifests[manifest_skill.name].append(
|
||||||
|
manifest_skill
|
||||||
|
)
|
||||||
|
self.skills_in_manifests['mycroft-audio-record'].append(ManifestSkill(
|
||||||
|
failure_message='',
|
||||||
|
installation='installed',
|
||||||
|
name='mycroft-audio-record'
|
||||||
|
))
|
||||||
|
|
||||||
|
def _build_response_data(self) -> dict:
|
||||||
|
install_statuses = {}
|
||||||
|
failure_reasons = {}
|
||||||
|
for skill_name, manifest_skills in self.skills_in_manifests.items():
|
||||||
|
skill_aggregator = SkillManifestAggregator(manifest_skills)
|
||||||
|
skill_aggregator.aggregate_manifest_skills()
|
||||||
|
if skill_aggregator.aggregate_skill.installation == 'failed':
|
||||||
|
failure_reasons[skill_name] = (
|
||||||
|
skill_aggregator.aggregate_skill.failure_message
|
||||||
|
)
|
||||||
|
install_statuses[skill_name] = (
|
||||||
|
skill_aggregator.aggregate_skill.installation
|
||||||
|
)
|
||||||
|
|
||||||
|
return dict(
|
||||||
|
installStatuses=install_statuses,
|
||||||
|
failureReasons=failure_reasons
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SkillManifestAggregator(object):
|
||||||
|
"""Base class containing functionality shared by summary and detail"""
|
||||||
|
|
||||||
|
def __init__(self, manifest_skills: List[ManifestSkill]):
|
||||||
|
self.manifest_skills = manifest_skills
|
||||||
|
self.aggregate_skill = ManifestSkill(
|
||||||
|
**asdict(manifest_skills[0]))
|
||||||
|
|
||||||
|
def aggregate_manifest_skills(self):
|
||||||
|
"""Aggregate skill data on all devices into a single skill.
|
||||||
|
|
||||||
|
Each skill is represented once on the Marketplace, even though it can
|
||||||
|
be present on multiple devices.
|
||||||
|
"""
|
||||||
|
self._validate_install_status()
|
||||||
|
self._determine_install_status()
|
||||||
|
if self.aggregate_skill.installation == 'failed':
|
||||||
|
self._determine_failure_reason()
|
||||||
|
|
||||||
|
def _validate_install_status(self):
|
||||||
|
for manifest_skill in self.manifest_skills:
|
||||||
|
if manifest_skill.installation not in VALID_INSTALLATION_VALUES:
|
||||||
|
raise ValueError(
|
||||||
|
'"{install_status}" is not a supported value of the '
|
||||||
|
'installation field in the skill manifest'.format(
|
||||||
|
install_status=manifest_skill.installation
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _determine_install_status(self):
|
||||||
|
"""Use skill data from all devices to determine install status.
|
||||||
|
|
||||||
|
When a skill is installed via the Marketplace, it is installed to all
|
||||||
|
devices. The Marketplace will not mark a skill as "installed" until
|
||||||
|
install is complete on all devices. Until that point, the status will
|
||||||
|
be "installing".
|
||||||
|
|
||||||
|
If the install fails on any device, the install will be flagged as a
|
||||||
|
failed install in the Marketplace.
|
||||||
|
"""
|
||||||
|
failed = [s.installation == 'failed' for s in
|
||||||
|
self.manifest_skills]
|
||||||
|
installing = [
|
||||||
|
s.installation == 'installing' for s in self.manifest_skills
|
||||||
|
]
|
||||||
|
uninstalling = [
|
||||||
|
s.installation == 'uninstalling' for s in
|
||||||
|
self.manifest_skills
|
||||||
|
]
|
||||||
|
installed = [
|
||||||
|
s.installation == 'installed' for s in self.manifest_skills
|
||||||
|
]
|
||||||
|
if any(failed):
|
||||||
|
self.aggregate_skill.installation = 'failed'
|
||||||
|
elif any(installing):
|
||||||
|
self.aggregate_skill.installation = 'installing'
|
||||||
|
elif any(uninstalling):
|
||||||
|
self.aggregate_skill.installation = 'uninstalling'
|
||||||
|
elif all(installed):
|
||||||
|
self.aggregate_skill.installation = 'installed'
|
||||||
|
|
||||||
|
def _determine_failure_reason(self):
|
||||||
|
"""When a skill fails to install, determine the reason"""
|
||||||
|
for manifest_skill in self.manifest_skills:
|
||||||
|
if manifest_skill.installation == 'failed':
|
||||||
|
self.aggregate_skill.failure_reason = (
|
||||||
|
manifest_skill.failure_message
|
||||||
|
)
|
||||||
|
break
|
|
@ -1,179 +0,0 @@
|
||||||
"""Endpoint to provide skill summary data to the marketplace."""
|
|
||||||
from collections import defaultdict
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from http import HTTPStatus
|
|
||||||
from logging import getLogger
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from markdown import markdown
|
|
||||||
import requests as service_request
|
|
||||||
|
|
||||||
from .common import (
|
|
||||||
aggregate_manifest_skills,
|
|
||||||
call_skill_manifest_endpoint,
|
|
||||||
parse_skill_manifest_response
|
|
||||||
)
|
|
||||||
from selene_util.api import APIError, SeleneEndpoint
|
|
||||||
|
|
||||||
DEFAULT_ICON_COLOR = '#6C7A89'
|
|
||||||
DEFAULT_ICON_NAME = 'comment-alt'
|
|
||||||
SYSTEM_CATEGORY = 'System'
|
|
||||||
UNDEFINED_CATEGORY = 'Not Categorized'
|
|
||||||
|
|
||||||
_log = getLogger(__package__)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RepositorySkill(object):
|
|
||||||
"""Represents a single skill defined in the Mycroft Skills repository."""
|
|
||||||
branch: str
|
|
||||||
categories: List[str]
|
|
||||||
created: str
|
|
||||||
credits: List[str]
|
|
||||||
description: str
|
|
||||||
id: str
|
|
||||||
last_update: str
|
|
||||||
platforms: List[str]
|
|
||||||
repository_owner: str
|
|
||||||
repository_url: str
|
|
||||||
skill_name: str
|
|
||||||
summary: str
|
|
||||||
tags: List[str]
|
|
||||||
title: str
|
|
||||||
triggers: List[str]
|
|
||||||
icon: dict
|
|
||||||
icon_image: str = field(default=None)
|
|
||||||
marketplace_category: str = field(init=False, default=UNDEFINED_CATEGORY)
|
|
||||||
|
|
||||||
def __post_init__(self):
|
|
||||||
if 'system' in self.tags:
|
|
||||||
self.marketplace_category = SYSTEM_CATEGORY
|
|
||||||
elif self.categories:
|
|
||||||
# a skill may have many categories. the first one in the
|
|
||||||
# list is considered the "primary" category. This is the
|
|
||||||
# category the marketplace will use to group the skill.
|
|
||||||
self.marketplace_category = self.categories[0]
|
|
||||||
if not self.icon:
|
|
||||||
self.icon = dict(icon=DEFAULT_ICON_NAME, color=DEFAULT_ICON_COLOR)
|
|
||||||
|
|
||||||
|
|
||||||
class SkillSummaryEndpoint(SeleneEndpoint):
|
|
||||||
authentication_required = False
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(SkillSummaryEndpoint, self).__init__()
|
|
||||||
self.available_skills: List[RepositorySkill] = []
|
|
||||||
self.response_skills = defaultdict(list)
|
|
||||||
self.skills_in_manifests = defaultdict(list)
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
try:
|
|
||||||
self._authenticate()
|
|
||||||
self._get_skills()
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self._build_response_data()
|
|
||||||
self.response = (self.response_skills, HTTPStatus.OK)
|
|
||||||
|
|
||||||
return self.response
|
|
||||||
|
|
||||||
def _get_skills(self):
|
|
||||||
"""Retrieve the skill data that will be used to build the response."""
|
|
||||||
self._get_available_skills()
|
|
||||||
# if self.authenticated:
|
|
||||||
# self._get_skill_manifests()
|
|
||||||
|
|
||||||
def _get_available_skills(self):
|
|
||||||
"""Retrieve all skills in the skill repository.
|
|
||||||
|
|
||||||
The data is retrieved from a database table that is populated with
|
|
||||||
the contents of a JSON object in the mycroft-skills-data Github
|
|
||||||
repository. The JSON object contains metadata about each skill.
|
|
||||||
"""
|
|
||||||
skill_service_response = service_request.get(
|
|
||||||
self.config['SELENE_BASE_URL'] + '/skill/all'
|
|
||||||
)
|
|
||||||
if skill_service_response.status_code != HTTPStatus.OK:
|
|
||||||
self._check_for_service_errors(skill_service_response)
|
|
||||||
self.available_skills = [
|
|
||||||
RepositorySkill(**skill) for skill in skill_service_response.json()
|
|
||||||
]
|
|
||||||
|
|
||||||
def _get_skill_manifests(self):
|
|
||||||
service_response = call_skill_manifest_endpoint(
|
|
||||||
self.tartarus_token,
|
|
||||||
self.config['TARTARUS_BASE_URL'],
|
|
||||||
self.user_uuid
|
|
||||||
)
|
|
||||||
if service_response.status_code != HTTPStatus.OK:
|
|
||||||
self._check_for_service_errors(service_response)
|
|
||||||
self.skills_in_manifest = parse_skill_manifest_response(
|
|
||||||
service_response
|
|
||||||
)
|
|
||||||
|
|
||||||
def _build_response_data(self):
|
|
||||||
"""Build the data to include in the response."""
|
|
||||||
if self.request.query_string:
|
|
||||||
skills_to_include = self._filter_skills()
|
|
||||||
else:
|
|
||||||
skills_to_include = self.available_skills
|
|
||||||
self._reformat_skills(skills_to_include)
|
|
||||||
self._sort_skills()
|
|
||||||
|
|
||||||
def _filter_skills(self) -> list:
|
|
||||||
"""If search criteria exist, only return those skills that match."""
|
|
||||||
skills_to_include = []
|
|
||||||
|
|
||||||
query_string = self.request.query_string.decode()
|
|
||||||
search_term = query_string.lower().split('=')[1]
|
|
||||||
for skill in self.available_skills:
|
|
||||||
search_term_match = (
|
|
||||||
search_term is None or
|
|
||||||
search_term in skill.title.lower() or
|
|
||||||
search_term in skill.description.lower() or
|
|
||||||
search_term in skill.summary.lower()
|
|
||||||
)
|
|
||||||
if skill.categories and not search_term_match:
|
|
||||||
search_term_match = (
|
|
||||||
search_term in skill.categories[0].lower()
|
|
||||||
)
|
|
||||||
for trigger in skill.triggers:
|
|
||||||
if search_term in trigger.lower():
|
|
||||||
search_term_match = True
|
|
||||||
if search_term_match:
|
|
||||||
skills_to_include.append(skill)
|
|
||||||
|
|
||||||
return skills_to_include
|
|
||||||
|
|
||||||
def _reformat_skills(self, skills_to_include: list):
|
|
||||||
"""Build the response data from the skill service response"""
|
|
||||||
for skill in skills_to_include:
|
|
||||||
install_status = None
|
|
||||||
manifest_skills = self.skills_in_manifests.get(skill.skill_name)
|
|
||||||
if skill.marketplace_category == SYSTEM_CATEGORY:
|
|
||||||
install_status = SYSTEM_CATEGORY.lower()
|
|
||||||
elif manifest_skills is not None:
|
|
||||||
aggregated_manifest = aggregate_manifest_skills(manifest_skills)
|
|
||||||
install_status = aggregated_manifest.installation
|
|
||||||
|
|
||||||
skill_summary = dict(
|
|
||||||
credits=skill.credits,
|
|
||||||
icon=skill.icon,
|
|
||||||
iconImage=skill.icon_image,
|
|
||||||
id=skill.id,
|
|
||||||
installStatus=install_status,
|
|
||||||
repositoryUrl=skill.repository_url,
|
|
||||||
summary=markdown(skill.summary, output_format='html5'),
|
|
||||||
title=skill.title,
|
|
||||||
triggers=skill.triggers
|
|
||||||
)
|
|
||||||
self.response_skills[skill.marketplace_category].append(
|
|
||||||
skill_summary
|
|
||||||
)
|
|
||||||
|
|
||||||
def _sort_skills(self):
|
|
||||||
"""Sort the skills in alphabetical order"""
|
|
||||||
for skill_category, skills in self.response_skills.items():
|
|
||||||
sorted_skills = sorted(skills, key=lambda skill: skill['title'])
|
|
||||||
self.response_skills[skill_category] = sorted_skills
|
|
Loading…
Reference in New Issue