Click to show feature link samples in admin page (#3280)
* Add feature_links_samples API * Add feature links samples UI * Fix code style
This commit is contained in:
Родитель
593968b95d
Коммит
34114d855a
|
@ -16,7 +16,7 @@
|
|||
from framework import basehandlers
|
||||
from internals.core_enums import *
|
||||
from internals.core_models import FeatureEntry
|
||||
from internals.feature_links import get_feature_links_summary, get_by_feature_id
|
||||
from internals.feature_links import get_feature_links_summary, get_by_feature_id, get_feature_links_samples
|
||||
from framework import permissions
|
||||
|
||||
class FeatureLinksAPI(basehandlers.APIHandler):
|
||||
|
@ -44,8 +44,19 @@ class FeatureLinksAPI(basehandlers.APIHandler):
|
|||
|
||||
|
||||
class FeatureLinksSummaryAPI(basehandlers.APIHandler):
|
||||
"""FeatureLinksSummaryAPI will return all links to the client."""
|
||||
"""FeatureLinksSummaryAPI will return summary of links to the client."""
|
||||
|
||||
@permissions.require_admin_site
|
||||
def do_get(self, **kwargs):
|
||||
return get_feature_links_summary()
|
||||
return get_feature_links_summary()
|
||||
|
||||
class FeatureLinksSamplesAPI(basehandlers.APIHandler):
|
||||
"""FeatureLinksSamplesAPI will return sample links to the client."""
|
||||
|
||||
@permissions.require_admin_site
|
||||
def do_get(self, **kwargs):
|
||||
domain = self.request.args.get('domain', None)
|
||||
type = self.request.args.get('type', None)
|
||||
is_error = self.get_bool_arg('is_error', None)
|
||||
if domain:
|
||||
return get_feature_links_samples(domain, type, is_error)
|
|
@ -1,4 +1,4 @@
|
|||
import {LitElement, css, html} from 'lit';
|
||||
import {LitElement, css, html, nothing} from 'lit';
|
||||
import {showToastMessage} from './utils.js';
|
||||
import {SHARED_STYLES} from '../css/shared-css.js';
|
||||
import {VARS} from '../css/_vars-css.js';
|
||||
|
@ -18,18 +18,29 @@ export class ChromedashAdminFeatureLinksPage extends LitElement {
|
|||
justify-content: space-between;
|
||||
font-size: 16px;
|
||||
}
|
||||
.feature-links-samples .line {
|
||||
background: rgb(232,234,237);
|
||||
}
|
||||
sl-icon-button::part(base) {
|
||||
padding: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
`];
|
||||
}
|
||||
static get properties() {
|
||||
return {
|
||||
loading: {type: Boolean},
|
||||
featureLinkSummary: {type: Object},
|
||||
sampleId: {type: String},
|
||||
samplesLoading: {type: Boolean},
|
||||
featureLinksSamples: {type: Array},
|
||||
featureLinksSummary: {type: Object},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.featureLinkSummary = [];
|
||||
this.featureLinksSummary = {};
|
||||
this.featureLinksSamples = [];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
@ -40,7 +51,7 @@ export class ChromedashAdminFeatureLinksPage extends LitElement {
|
|||
async fetchData() {
|
||||
try {
|
||||
this.loading = true;
|
||||
this.featureLinkSummary = await window.csClient.getFeatureLinkSummary();
|
||||
this.featureLinksSummary = await window.csClient.getFeatureLinksSummary();
|
||||
} catch {
|
||||
showToastMessage('Some errors occurred. Please refresh the page or try again later.');
|
||||
} finally {
|
||||
|
@ -48,34 +59,92 @@ export class ChromedashAdminFeatureLinksPage extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
calcSampleId(domain, type, isError) {
|
||||
return `domain=${domain}&type=${type}&isError=${isError}`;
|
||||
}
|
||||
|
||||
async fetchLinkSamples(domain, type, isError) {
|
||||
this.sampleId = this.calcSampleId(domain, type, isError);
|
||||
this.featureLinksSamples = [];
|
||||
this.samplesLoading = true;
|
||||
try {
|
||||
this.featureLinksSamples = await
|
||||
window.csClient.getFeatureLinksSamples(domain, type, isError);
|
||||
} catch {
|
||||
showToastMessage('Some errors occurred. Please refresh the page or try again later.');
|
||||
} finally {
|
||||
this.samplesLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
renderSamples() {
|
||||
if (this.samplesLoading) {
|
||||
return html`<sl-spinner></sl-spinner>`;
|
||||
}
|
||||
return html`
|
||||
<div class="feature-links-samples">
|
||||
${this.featureLinksSamples.map((sample) => html`
|
||||
<div class="line">
|
||||
<div>
|
||||
<a href=${sample.url}><i>${sample.url}</i></a>
|
||||
${sample.http_error_code ? html`<i>(${sample.http_error_code})</i>` : nothing}
|
||||
</div>
|
||||
<a href=${`/feature/${sample.feature_ids}`} target="_blank" rel="noopener">
|
||||
<sl-icon library="material" name="link" slot="prefix" title="linked feature"></sl-icon>
|
||||
</a>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
renderComponents() {
|
||||
return html`
|
||||
<div class="feature-links-summary">
|
||||
<sl-details summary="Link Summary" open>
|
||||
<div class="line">All Links <b>${this.featureLinkSummary.total_count}</b></div>
|
||||
<div class="line">Covered Links <b>${this.featureLinkSummary.covered_count}</b></div>
|
||||
<div class="line">Uncovered (aka "web") Links <b>${this.featureLinkSummary.uncovered_count}</b></div>
|
||||
<div class="line">All Error Links<b>${this.featureLinkSummary.error_count}</b></div>
|
||||
<div class="line">HTTP Error Links<b>${this.featureLinkSummary.http_error_count}</b></div>
|
||||
<div class="line">All Links <b>${this.featureLinksSummary.total_count}</b></div>
|
||||
<div class="line">Covered Links <b>${this.featureLinksSummary.covered_count}</b></div>
|
||||
<div class="line">Uncovered (aka "web") Links <b>${this.featureLinksSummary.uncovered_count}</b></div>
|
||||
<div class="line">All Error Links<b>${this.featureLinksSummary.error_count}</b></div>
|
||||
<div class="line">HTTP Error Links<b>${this.featureLinksSummary.http_error_count}</b></div>
|
||||
</sl-details>
|
||||
<sl-details summary="Link Types" open>
|
||||
${this.featureLinkSummary.link_types.map((linkType) => html`
|
||||
${this.featureLinksSummary.link_types.map((linkType) => html`
|
||||
<div class="line">${(linkType.key).toUpperCase()} <b>${linkType.count}</b></div>
|
||||
`)}
|
||||
</sl-details>
|
||||
<sl-details summary="Uncovered Link Domains" open>
|
||||
${this.featureLinkSummary.uncovered_link_domains.map((domain) => html`
|
||||
<div class="line"><a href=${domain.key}>${domain.key}</a> <b>${domain.count}</b></div>
|
||||
${this.featureLinksSummary.uncovered_link_domains.map((domain) => html`
|
||||
<div class="line">
|
||||
<div>
|
||||
<a href=${domain.key}>${domain.key}</a>
|
||||
<sl-icon-button library="material" name="search" slot="prefix" title="Samples"
|
||||
@click=${() => this.fetchLinkSamples(domain.key, 'web', undefined)}>
|
||||
></sl-icon-button>
|
||||
</div>
|
||||
<b>${domain.count}</b>
|
||||
</div>
|
||||
${this.sampleId === this.calcSampleId(domain.key, 'web', undefined) ? this.renderSamples() : nothing}
|
||||
`)}
|
||||
</sl-details>
|
||||
<sl-details summary="Error Link Domains" open>
|
||||
${this.featureLinkSummary.error_link_domains.map((domain) => html`
|
||||
<div class="line"><a href=${domain.key}>${domain.key}</a> <b>${domain.count}</b></div>
|
||||
`)}
|
||||
${this.featureLinksSummary.error_link_domains.map((domain) => html`
|
||||
<div class="line">
|
||||
<div>
|
||||
<a href=${domain.key}>${domain.key}</a>
|
||||
<sl-icon-button library="material" name="search" slot="prefix" title="Samples"
|
||||
@click=${() => this.fetchLinkSamples(domain.key, undefined, true)}>
|
||||
></sl-icon-button>
|
||||
</div>
|
||||
<b>${domain.count}</b>
|
||||
</div>
|
||||
${this.sampleId === this.calcSampleId(domain.key, undefined, true) ? this.renderSamples() : nothing}
|
||||
`)}
|
||||
</sl-details>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.loading ?
|
||||
|
|
|
@ -312,10 +312,21 @@ class ChromeStatusClient {
|
|||
return this.doGet(`/feature_links?feature_id=${featureId}&update_stale_links=${updateStaleLinks}`);
|
||||
}
|
||||
|
||||
getFeatureLinkSummary() {
|
||||
getFeatureLinksSummary() {
|
||||
return this.doGet('/feature_links_summary');
|
||||
}
|
||||
|
||||
getFeatureLinksSamples(domain, type, isError) {
|
||||
let optionalParams = '';
|
||||
if (type) {
|
||||
optionalParams += `&type=${type}`;
|
||||
}
|
||||
if (isError !== undefined && isError !== null) {
|
||||
optionalParams += `&is_error=${isError}`;
|
||||
}
|
||||
return this.doGet(`/feature_links_samples?domain=${domain}${optionalParams}`);
|
||||
}
|
||||
|
||||
// Stages API
|
||||
getStage(featureId, stageId) {
|
||||
return this.doGet(`/features/${featureId}/stages/${stageId}`);
|
||||
|
|
|
@ -288,6 +288,39 @@ def get_feature_links_summary():
|
|||
}
|
||||
|
||||
|
||||
def get_feature_links_samples(domain: str, type: str | None, is_error: bool | None):
|
||||
"""retrieves a list of feature links based on the specified domain, type, and error status."""
|
||||
|
||||
MAX_SAMPLES = 100
|
||||
filters = [
|
||||
FeatureLinks.url >= domain,
|
||||
]
|
||||
if type:
|
||||
filters.append(FeatureLinks.type == type)
|
||||
if is_error:
|
||||
filters.append(FeatureLinks.is_error == is_error)
|
||||
feature_links = FeatureLinks.query(
|
||||
*filters
|
||||
).fetch(MAX_SAMPLES)
|
||||
|
||||
# filter out links that do not start with the specified domain and convert to dict
|
||||
feature_links = [
|
||||
fl.to_dict(include=['url', 'type', 'feature_ids', 'is_error', 'http_error_code'])
|
||||
for fl in feature_links if fl.url.startswith(domain)
|
||||
]
|
||||
|
||||
# flatten feature links like projection in get_feature_links_summary
|
||||
flattened_feature_links = []
|
||||
for feature_link in feature_links:
|
||||
for feature_id in feature_link['feature_ids']:
|
||||
flattened_feature_links.append({
|
||||
**feature_link,
|
||||
'feature_ids': feature_id
|
||||
})
|
||||
|
||||
return flattened_feature_links
|
||||
|
||||
|
||||
class UpdateAllFeatureLinksHandlers(FlaskHandler):
|
||||
|
||||
def get_template_data(self, **kwargs) -> str:
|
||||
|
|
1
main.py
1
main.py
|
@ -107,6 +107,7 @@ api_routes: list[Route] = [
|
|||
Route(f'{API_BASE}/features/create', features_api.FeaturesAPI),
|
||||
Route(f'{API_BASE}/feature_links', feature_links_api.FeatureLinksAPI),
|
||||
Route(f'{API_BASE}/feature_links_summary', feature_links_api.FeatureLinksSummaryAPI),
|
||||
Route(f'{API_BASE}/feature_links_samples', feature_links_api.FeatureLinksSamplesAPI),
|
||||
Route(f'{API_BASE}/features/<int:feature_id>/votes',
|
||||
reviews_api.VotesAPI),
|
||||
Route(f'{API_BASE}/features/<int:feature_id>/votes/<int:gate_id>',
|
||||
|
|
Загрузка…
Ссылка в новой задаче