mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 08:42:41 +01:00
Add aggregated facts to the Facts application
Summary: Some facts are aggregations of other facts. For example, we may compute how many times each macro is used in each object as a "raw fact": Dnnn uses macro "psyduck" 6 times. But we want to present this data in aggregate form, e.g. "order macros by popularity". We can do this at runtime and it probably won't be too awful a query, but we can also aggregate it cheaply: Macro "psyduck" is used 3920 times across all objects. ...and then do a query like "select macros ordered by usage". "Aggregate" facts support facts like this. The aggregate facts I've implemented are: - Count of all objects. - Count of objects of type X. - Last time facts were updated. These clearly fit the "aggregate" facts template well. I'm not 100% sure macros do. We can use this table to answer a question like "What are the most popular macros, ordered by use?" We can also use it to answer a question like "What are the most popular macros in the last 6 months?", if we build a specific fact for that. But we can't use it to answer a question like "What are the most popular macros between times X and Y?". Maybe that's important; maybe not. This seems like a good fit for at least some types of facts. I'll de-magic the keys a bit in the next diff. Test Plan: Ran the engines and got some aggregated facts about other facts. Reviewers: vrana, btrahan Reviewed By: vrana CC: aran Maniphest Tasks: T1562 Differential Revision: https://secure.phabricator.com/D3089
This commit is contained in:
parent
05bf6bef81
commit
486f7c1e8e
13 changed files with 262 additions and 0 deletions
|
@ -624,10 +624,14 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php',
|
'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php',
|
||||||
'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php',
|
'PhabricatorEventType' => 'infrastructure/events/constant/PhabricatorEventType.php',
|
||||||
'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php',
|
'PhabricatorExampleEventListener' => 'infrastructure/events/PhabricatorExampleEventListener.php',
|
||||||
|
'PhabricatorFactAggregate' => 'applications/fact/storage/PhabricatorFactAggregate.php',
|
||||||
|
'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php',
|
||||||
'PhabricatorFactCountEngine' => 'applications/fact/engine/PhabricatorFactCountEngine.php',
|
'PhabricatorFactCountEngine' => 'applications/fact/engine/PhabricatorFactCountEngine.php',
|
||||||
'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php',
|
'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php',
|
||||||
'PhabricatorFactDaemon' => 'applications/fact/daemon/PhabricatorFactDaemon.php',
|
'PhabricatorFactDaemon' => 'applications/fact/daemon/PhabricatorFactDaemon.php',
|
||||||
'PhabricatorFactEngine' => 'applications/fact/engine/PhabricatorFactEngine.php',
|
'PhabricatorFactEngine' => 'applications/fact/engine/PhabricatorFactEngine.php',
|
||||||
|
'PhabricatorFactHomeController' => 'applications/fact/controller/PhabricatorFactHomeController.php',
|
||||||
|
'PhabricatorFactLastUpdatedEngine' => 'applications/fact/engine/PhabricatorFactLastUpdatedEngine.php',
|
||||||
'PhabricatorFactManagementAnalyzeWorkflow' => 'applications/fact/management/PhabricatorFactManagementAnalyzeWorkflow.php',
|
'PhabricatorFactManagementAnalyzeWorkflow' => 'applications/fact/management/PhabricatorFactManagementAnalyzeWorkflow.php',
|
||||||
'PhabricatorFactManagementDestroyWorkflow' => 'applications/fact/management/PhabricatorFactManagementDestroyWorkflow.php',
|
'PhabricatorFactManagementDestroyWorkflow' => 'applications/fact/management/PhabricatorFactManagementDestroyWorkflow.php',
|
||||||
'PhabricatorFactManagementListWorkflow' => 'applications/fact/management/PhabricatorFactManagementListWorkflow.php',
|
'PhabricatorFactManagementListWorkflow' => 'applications/fact/management/PhabricatorFactManagementListWorkflow.php',
|
||||||
|
@ -1660,9 +1664,13 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorEvent' => 'PhutilEvent',
|
'PhabricatorEvent' => 'PhutilEvent',
|
||||||
'PhabricatorEventType' => 'PhutilEventType',
|
'PhabricatorEventType' => 'PhutilEventType',
|
||||||
'PhabricatorExampleEventListener' => 'PhutilEventListener',
|
'PhabricatorExampleEventListener' => 'PhutilEventListener',
|
||||||
|
'PhabricatorFactAggregate' => 'PhabricatorFactDAO',
|
||||||
|
'PhabricatorFactController' => 'PhabricatorController',
|
||||||
'PhabricatorFactCountEngine' => 'PhabricatorFactEngine',
|
'PhabricatorFactCountEngine' => 'PhabricatorFactEngine',
|
||||||
'PhabricatorFactDAO' => 'PhabricatorLiskDAO',
|
'PhabricatorFactDAO' => 'PhabricatorLiskDAO',
|
||||||
'PhabricatorFactDaemon' => 'PhabricatorDaemon',
|
'PhabricatorFactDaemon' => 'PhabricatorDaemon',
|
||||||
|
'PhabricatorFactHomeController' => 'PhabricatorFactController',
|
||||||
|
'PhabricatorFactLastUpdatedEngine' => 'PhabricatorFactEngine',
|
||||||
'PhabricatorFactManagementAnalyzeWorkflow' => 'PhabricatorFactManagementWorkflow',
|
'PhabricatorFactManagementAnalyzeWorkflow' => 'PhabricatorFactManagementWorkflow',
|
||||||
'PhabricatorFactManagementDestroyWorkflow' => 'PhabricatorFactManagementWorkflow',
|
'PhabricatorFactManagementDestroyWorkflow' => 'PhabricatorFactManagementWorkflow',
|
||||||
'PhabricatorFactManagementListWorkflow' => 'PhabricatorFactManagementWorkflow',
|
'PhabricatorFactManagementListWorkflow' => 'PhabricatorFactManagementWorkflow',
|
||||||
|
|
|
@ -463,6 +463,10 @@ class AphrontDefaultApplicationConfiguration
|
||||||
|
|
||||||
'/emailverify/(?P<code>[^/]+)/' =>
|
'/emailverify/(?P<code>[^/]+)/' =>
|
||||||
'PhabricatorEmailVerificationController',
|
'PhabricatorEmailVerificationController',
|
||||||
|
|
||||||
|
'/fact/' => array(
|
||||||
|
'' => 'PhabricatorFactHomeController',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abstract class PhabricatorFactController extends PhabricatorController {
|
||||||
|
|
||||||
|
public function buildStandardPageResponse($view, array $data) {
|
||||||
|
$page = $this->buildStandardPageView();
|
||||||
|
|
||||||
|
$page->setBaseURI('/fact/');
|
||||||
|
$page->setTitle(idx($data, 'title'));
|
||||||
|
|
||||||
|
$page->setGlyph("\xCE\xA3");
|
||||||
|
$page->appendChild($view);
|
||||||
|
|
||||||
|
$response = new AphrontWebpageResponse();
|
||||||
|
return $response->setContent($page->render());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class PhabricatorFactHomeController extends PhabricatorFactController {
|
||||||
|
|
||||||
|
public function processRequest() {
|
||||||
|
|
||||||
|
$facts = id(new PhabricatorFactAggregate())->loadAllWhere(
|
||||||
|
'factType LIKE %> OR factType = %s',
|
||||||
|
'+N:',
|
||||||
|
'updated');
|
||||||
|
|
||||||
|
$rows = array();
|
||||||
|
foreach ($facts as $fact) {
|
||||||
|
$rows[] = array(
|
||||||
|
phutil_escape_html($fact->getFactType()),
|
||||||
|
(int)$fact->getValueX(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = new AphrontTableView($rows);
|
||||||
|
$table->setHeaders(
|
||||||
|
array(
|
||||||
|
'Fact',
|
||||||
|
'Value',
|
||||||
|
));
|
||||||
|
$table->setColumnClasses(
|
||||||
|
array(
|
||||||
|
'wide',
|
||||||
|
'n',
|
||||||
|
));
|
||||||
|
|
||||||
|
$panel = new AphrontPanelView();
|
||||||
|
$panel->setHeader('Facts!');
|
||||||
|
$panel->appendChild($table);
|
||||||
|
|
||||||
|
return $this->buildStandardPageResponse(
|
||||||
|
$panel,
|
||||||
|
array(
|
||||||
|
'title' => 'Facts!',
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -54,6 +54,22 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function processAggregates() {
|
||||||
|
$facts = $this->computeAggregateFacts();
|
||||||
|
$this->updateAggregateFacts($facts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function computeAggregateFacts() {
|
||||||
|
$facts = array();
|
||||||
|
foreach ($this->engines as $engine) {
|
||||||
|
if (!$engine->shouldComputeAggregateFacts()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$facts[] = $engine->computeAggregateFacts();
|
||||||
|
}
|
||||||
|
return array_mergev($facts);
|
||||||
|
}
|
||||||
|
|
||||||
private function computeRawFacts(PhabricatorLiskDAO $object) {
|
private function computeRawFacts(PhabricatorLiskDAO $object) {
|
||||||
$facts = array();
|
$facts = array();
|
||||||
foreach ($this->engines as $engine) {
|
foreach ($this->engines as $engine) {
|
||||||
|
@ -118,4 +134,34 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
|
||||||
$table->saveTransaction();
|
$table->saveTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function updateAggregateFacts(array $facts) {
|
||||||
|
if (!$facts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = new PhabricatorFactAggregate();
|
||||||
|
$conn = $table->establishConnection('w');
|
||||||
|
$table_name = $table->getTableName();
|
||||||
|
|
||||||
|
$sql = array();
|
||||||
|
foreach ($facts as $fact) {
|
||||||
|
$sql[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'(%s, %s, %d)',
|
||||||
|
$fact->getFactType(),
|
||||||
|
$fact->getObjectPHID(),
|
||||||
|
$fact->getValueX());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_chunk($sql, 256) as $chunk) {
|
||||||
|
queryfx(
|
||||||
|
$conn,
|
||||||
|
'INSERT INTO %T (factType, objectPHID, valueX) VALUES %Q
|
||||||
|
ON DUPLICATE KEY UPDATE valueX = VALUES(valueX)',
|
||||||
|
$table_name,
|
||||||
|
implode(', ', $chunk));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,4 +41,31 @@ final class PhabricatorFactCountEngine extends PhabricatorFactEngine {
|
||||||
return $facts;
|
return $facts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function shouldComputeAggregateFacts() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function computeAggregateFacts() {
|
||||||
|
$table = new PhabricatorFactRaw();
|
||||||
|
$table_name = $table->getTableName();
|
||||||
|
$conn = $table->establishConnection('r');
|
||||||
|
|
||||||
|
$counts = queryfx_all(
|
||||||
|
$conn,
|
||||||
|
'SELECT factType, count(*) N FROM %T WHERE factType LIKE %>
|
||||||
|
GROUP BY factType',
|
||||||
|
$table_name,
|
||||||
|
'N:');
|
||||||
|
|
||||||
|
$facts = array();
|
||||||
|
foreach ($counts as $count) {
|
||||||
|
$facts[] = id(new PhabricatorFactAggregate())
|
||||||
|
->setFactType('+'.$count['factType'])
|
||||||
|
->setValueX($count['N']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $facts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,4 +40,12 @@ abstract class PhabricatorFactEngine {
|
||||||
return array();
|
return array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function shouldComputeAggregateFacts() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function computeAggregateFacts() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Engine that records the time facts were last updated.
|
||||||
|
*/
|
||||||
|
final class PhabricatorFactLastUpdatedEngine extends PhabricatorFactEngine {
|
||||||
|
|
||||||
|
public function shouldComputeAggregateFacts() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function computeAggregateFacts() {
|
||||||
|
$facts = array();
|
||||||
|
|
||||||
|
$facts[] = id(new PhabricatorFactAggregate())
|
||||||
|
->setFactType('updated')
|
||||||
|
->setValueX(time());
|
||||||
|
|
||||||
|
return $facts;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -41,6 +41,8 @@ final class PhabricatorFactManagementAnalyzeWorkflow
|
||||||
$daemon->processIterator($iterator);
|
$daemon->processIterator($iterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$daemon->processAggregates();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ final class PhabricatorFactManagementDestroyWorkflow
|
||||||
|
|
||||||
$tables = array();
|
$tables = array();
|
||||||
$tables[] = new PhabricatorFactRaw();
|
$tables[] = new PhabricatorFactRaw();
|
||||||
|
$tables[] = new PhabricatorFactAggregate();
|
||||||
foreach ($tables as $table) {
|
foreach ($tables as $table) {
|
||||||
$conn = $table->establishConnection('w');
|
$conn = $table->establishConnection('w');
|
||||||
$name = $table->getTableName();
|
$name = $table->getTableName();
|
||||||
|
|
|
@ -31,6 +31,7 @@ final class PhabricatorFactManagementStatusWorkflow
|
||||||
|
|
||||||
$map = array(
|
$map = array(
|
||||||
'raw' => new PhabricatorFactRaw(),
|
'raw' => new PhabricatorFactRaw(),
|
||||||
|
'agg' => new PhabricatorFactAggregate(),
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($map as $type => $table) {
|
foreach ($map as $type => $table) {
|
||||||
|
@ -48,6 +49,9 @@ final class PhabricatorFactManagementStatusWorkflow
|
||||||
case 'raw':
|
case 'raw':
|
||||||
$desc = pht('There are %d raw fact(s) in storage.', $n);
|
$desc = pht('There are %d raw fact(s) in storage.', $n);
|
||||||
break;
|
break;
|
||||||
|
case 'agg':
|
||||||
|
$desc = pht('There are %d aggregate fact(s) in storage.', $n);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$console->writeOut("%s\n", $desc);
|
$console->writeOut("%s\n", $desc);
|
||||||
|
|
26
src/applications/fact/storage/PhabricatorFactAggregate.php
Normal file
26
src/applications/fact/storage/PhabricatorFactAggregate.php
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class PhabricatorFactAggregate extends PhabricatorFactDAO {
|
||||||
|
|
||||||
|
protected $id;
|
||||||
|
protected $factType;
|
||||||
|
protected $objectPHID;
|
||||||
|
protected $valueX;
|
||||||
|
|
||||||
|
}
|
|
@ -126,6 +126,11 @@ abstract class PhabricatorBaseEnglishTranslation
|
||||||
'There are %d raw facts in storage.',
|
'There are %d raw facts in storage.',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
'There are %d aggregate fact(s) in storage.' => array(
|
||||||
|
'There is %d aggregate fact in storage.',
|
||||||
|
'There are %d aggregate facts in storage.',
|
||||||
|
),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue