From 861bc09eeb39c677c8791983d65246d4ebda3b4c Mon Sep 17 00:00:00 2001 From: Maxim Pimenov Date: Fri, 12 Apr 2019 14:54:48 +0300 Subject: [PATCH] [search] [assessment-tool] Samples can now be marked as useless. --- .../assessment_tool/context.cpp | 21 ++++++++- .../assessment_tool/context.hpp | 22 ++++++++-- .../search_quality/assessment_tool/edits.hpp | 32 ++++++++++++++ .../assessment_tool/main_model.cpp | 43 +++++++++++++------ .../assessment_tool/main_model.hpp | 2 + .../assessment_tool/main_view.cpp | 27 +++++++----- .../assessment_tool/main_view.hpp | 7 +-- .../search_quality/assessment_tool/model.hpp | 1 + .../assessment_tool/sample_view.cpp | 27 ++++++++++++ .../assessment_tool/sample_view.hpp | 4 ++ .../assessment_tool/samples_view.cpp | 22 ++++++++++ .../assessment_tool/samples_view.hpp | 11 +++++ .../search_quality/assessment_tool/view.hpp | 5 ++- search/search_quality/sample.cpp | 3 ++ search/search_quality/sample.hpp | 5 +++ 15 files changed, 200 insertions(+), 32 deletions(-) diff --git a/search/search_quality/assessment_tool/context.cpp b/search/search_quality/assessment_tool/context.cpp index 8a968b2a4c..15d32d473c 100644 --- a/search/search_quality/assessment_tool/context.cpp +++ b/search/search_quality/assessment_tool/context.cpp @@ -21,9 +21,18 @@ void Context::Clear() m_nonFoundResults.clear(); m_nonFoundResultsEdits.Clear(); + m_sampleEdits.Clear(); + m_initialized = false; } +void Context::LoadFromSample(search::Sample const & sample) +{ + Clear(); + m_sample = sample; + m_sampleEdits.Reset(sample.m_useless); +} + search::Sample Context::MakeSample(search::FeatureLoader & loader) const { search::Sample outSample = m_sample; @@ -93,6 +102,8 @@ search::Sample Context::MakeSample(search::FeatureLoader & loader) const outResults.push_back(search::Sample::Result::Build(*ft, *foundEntries[i].m_currRelevance)); } + outSample.m_useless = m_sampleEdits.m_currUseless; + return outSample; } @@ -102,12 +113,15 @@ void Context::ApplyEdits() return; m_foundResultsEdits.Apply(); m_nonFoundResultsEdits.Apply(); + m_sampleEdits.Apply(); } // ContextList ------------------------------------------------------------------------------------- -ContextList::ContextList(OnUpdate onResultsUpdate, OnUpdate onNonFoundResultsUpdate) +ContextList::ContextList(OnUpdate onResultsUpdate, OnUpdate onNonFoundResultsUpdate, + OnSampleUpdate onSampleUpdate) : m_onResultsUpdate(onResultsUpdate) , m_onNonFoundResultsUpdate(onNonFoundResultsUpdate) + , m_onSampleUpdate(onSampleUpdate) { } @@ -133,6 +147,11 @@ void ContextList::Resize(size_t size) OnContextUpdated(i); if (m_onNonFoundResultsUpdate) m_onNonFoundResultsUpdate(i, update); + }, + [this, i]() { + OnContextUpdated(i); + if (m_onSampleUpdate) + m_onSampleUpdate(i); }); } } diff --git a/search/search_quality/assessment_tool/context.hpp b/search/search_quality/assessment_tool/context.hpp index 35d111771d..1b86975958 100644 --- a/search/search_quality/assessment_tool/context.hpp +++ b/search/search_quality/assessment_tool/context.hpp @@ -26,8 +26,11 @@ struct Context Completed }; - Context(Edits::OnUpdate onFoundResultsUpdate, Edits::OnUpdate onNonFoundResultsUpdate) - : m_foundResultsEdits(onFoundResultsUpdate), m_nonFoundResultsEdits(onNonFoundResultsUpdate) + Context(Edits::OnUpdate onFoundResultsUpdate, Edits::OnUpdate onNonFoundResultsUpdate, + SampleEdits::OnUpdate onSampleUpdate) + : m_foundResultsEdits(onFoundResultsUpdate) + , m_nonFoundResultsEdits(onNonFoundResultsUpdate) + , m_sampleEdits(onSampleUpdate) { } @@ -42,8 +45,12 @@ struct Context m_nonFoundResultsEdits.Add(result.m_relevance); } + bool IsUseless() const { return m_sampleEdits.m_currUseless; } + bool HasChanges() const { + if (m_sampleEdits.HasChanges()) + return true; if (!m_initialized) return false; return m_foundResultsEdits.HasChanges() || m_nonFoundResultsEdits.HasChanges(); @@ -51,6 +58,8 @@ struct Context void Clear(); + void LoadFromSample(search::Sample const & sample); + // Makes sample in accordance with uncommited edits. search::Sample MakeSample(search::FeatureLoader & loader) const; @@ -67,6 +76,8 @@ struct Context std::vector m_nonFoundResults; Edits m_nonFoundResultsEdits; + SampleEdits m_sampleEdits; + SearchState m_searchState = SearchState::Untouched; bool m_initialized = false; @@ -95,6 +106,8 @@ public: return (*m_contexts)[index].m_searchState; } + bool IsUseless(size_t index) const { return (*m_contexts)[index].m_sampleEdits.m_currUseless; } + size_t Size() const { return m_contexts->Size(); } private: @@ -102,8 +115,10 @@ public: }; using OnUpdate = std::function; + using OnSampleUpdate = std::function; - ContextList(OnUpdate onResultsUpdate, OnUpdate onNonFoundResultsUpdate); + ContextList(OnUpdate onResultsUpdate, OnUpdate onNonFoundResultsUpdate, + OnSampleUpdate onSampleUpdate); void Resize(size_t size); size_t Size() const { return m_contexts.size(); } @@ -128,4 +143,5 @@ private: OnUpdate m_onResultsUpdate; OnUpdate m_onNonFoundResultsUpdate; + OnSampleUpdate m_onSampleUpdate; }; diff --git a/search/search_quality/assessment_tool/edits.hpp b/search/search_quality/assessment_tool/edits.hpp index 3e782f7d98..34e2508a6a 100644 --- a/search/search_quality/assessment_tool/edits.hpp +++ b/search/search_quality/assessment_tool/edits.hpp @@ -12,6 +12,38 @@ #include +struct SampleEdits +{ + using OnUpdate = std::function; + + SampleEdits(OnUpdate onUpdate) : m_onUpdate(onUpdate) {} + + void Reset(bool origUseless) + { + m_origUseless = origUseless; + m_currUseless = origUseless; + } + + void FlipUsefulness() + { + m_currUseless ^= true; + if (m_onUpdate) + m_onUpdate(); + } + + void Apply() { m_origUseless = m_currUseless; } + + bool HasChanges() const { return m_origUseless != m_currUseless; } + + void Clear() {} + + bool m_origUseless = false; + bool m_currUseless = false; + + OnUpdate m_onUpdate; +}; + +// todo(@m) Rename to ResultsEdits? class Edits { public: diff --git a/search/search_quality/assessment_tool/main_model.cpp b/search/search_quality/assessment_tool/main_model.cpp index 802e757d5c..a22e82cf7e 100644 --- a/search/search_quality/assessment_tool/main_model.cpp +++ b/search/search_quality/assessment_tool/main_model.cpp @@ -36,12 +36,13 @@ MainModel::MainModel(Framework & framework) }, [this](size_t sampleIndex, Edits::Update const & update) { OnUpdate(View::ResultType::NonFound, sampleIndex, update); - }) + }, + [this](size_t sampleIndex) { OnSampleUpdate(sampleIndex); }) , m_runner(m_framework, m_dataSource, m_contexts, [this](search::Results const & results) { UpdateViewOnResults(results); }, [this](size_t index) { - // The second parameter does not matter, we only change SearchStatus. - m_view->OnSampleChanged(index, false /* hasEdits */); + // Only the first parameter matters because we only change SearchStatus. + m_view->OnSampleChanged(index, false /* isUseless */, false /* hasEdits */); }) { search::CheckLocale(); @@ -77,11 +78,8 @@ void MainModel::Open(string const & path) m_contexts.Resize(samples.size()); for (size_t i = 0; i < samples.size(); ++i) - { - auto & context = m_contexts[i]; - context.Clear(); - context.m_sample = samples[i]; - } + m_contexts[i].LoadFromSample(samples[i]); + m_path = path; m_view->SetSamples(ContextList::SamplesSlice(m_contexts)); @@ -134,7 +132,7 @@ void MainModel::OnSampleSelected(int index) auto & context = m_contexts[index]; auto const & sample = context.m_sample; - m_view->ShowSample(index, sample, sample.m_pos, context.HasChanges()); + m_view->ShowSample(index, sample, sample.m_pos, context.IsUseless(), context.HasChanges()); m_runner.ResetForegroundSearch(); m_numShownResults = 0; @@ -271,12 +269,22 @@ void MainModel::AddNonFoundResult(FeatureID const & id) context.AddNonFoundResult(result); } +void MainModel::FlipSampleUsefulness(int index) +{ + CHECK_EQUAL(m_selectedSample, index, ()); + + m_contexts[index].m_sampleEdits.FlipUsefulness(); + + // Don't bother with resetting search: we cannot tell whether + // the sample is useless without its results anyway. +} + void MainModel::InitiateForegroundSearch(size_t index) { auto & context = m_contexts[index]; auto const & sample = context.m_sample; - m_view->ShowSample(index, sample, sample.m_pos, context.HasChanges()); + m_view->ShowSample(index, sample, sample.m_pos, context.IsUseless(), context.HasChanges()); m_runner.InitiateForegroundSearch(index); m_view->OnSearchStarted(); } @@ -299,7 +307,7 @@ void MainModel::OnUpdate(View::ResultType type, size_t sampleIndex, Edits::Updat } m_view->OnResultChanged(sampleIndex, type, update); - m_view->OnSampleChanged(sampleIndex, context.HasChanges()); + m_view->OnSampleChanged(sampleIndex, context.IsUseless(), context.HasChanges()); m_view->OnSamplesChanged(m_contexts.HasChanges()); if (update.m_type == Type::Add || update.m_type == Type::Resurrect || @@ -311,6 +319,14 @@ void MainModel::OnUpdate(View::ResultType type, size_t sampleIndex, Edits::Updat } } +void MainModel::OnSampleUpdate(size_t sampleIndex) +{ + auto & context = m_contexts[sampleIndex]; + + m_view->OnSampleChanged(sampleIndex, context.IsUseless(), context.HasChanges()); + m_view->OnSamplesChanged(m_contexts.HasChanges()); +} + void MainModel::UpdateViewOnResults(search::Results const & results) { CHECK(m_threadChecker.CalledOnOriginalThread(), ()); @@ -329,7 +345,8 @@ void MainModel::UpdateViewOnResults(search::Results const & results) m_view->ShowMarks(context); m_view->OnResultChanged(m_selectedSample, View::ResultType::Found, Edits::Update::MakeAll()); m_view->OnResultChanged(m_selectedSample, View::ResultType::NonFound, Edits::Update::MakeAll()); - m_view->OnSampleChanged(m_selectedSample, context.HasChanges()); + m_view->OnSampleChanged(m_selectedSample, context.IsUseless(), context.HasChanges()); + m_view->OnSamplesChanged(m_contexts.HasChanges()); m_view->SetEdits(m_selectedSample, context.m_foundResultsEdits, context.m_nonFoundResultsEdits); m_view->OnSearchCompleted(); @@ -346,7 +363,7 @@ void MainModel::OnChangeAllRelevancesClicked(Edits::Relevance relevance) m_view->OnResultChanged(m_selectedSample, View::ResultType::Found, Edits::Update::MakeAll()); m_view->OnResultChanged(m_selectedSample, View::ResultType::NonFound, Edits::Update::MakeAll()); - m_view->OnSampleChanged(m_selectedSample, context.HasChanges()); + m_view->OnSampleChanged(m_selectedSample, context.IsUseless(), context.HasChanges()); m_view->OnSamplesChanged(m_contexts.HasChanges()); } diff --git a/search/search_quality/assessment_tool/main_model.hpp b/search/search_quality/assessment_tool/main_model.hpp index 78079c4b35..4f5ba0eff7 100644 --- a/search/search_quality/assessment_tool/main_model.hpp +++ b/search/search_quality/assessment_tool/main_model.hpp @@ -47,6 +47,7 @@ public: bool HasChanges() override; bool AlreadyInSamples(FeatureID const & id) override; void AddNonFoundResult(FeatureID const & id) override; + void FlipSampleUsefulness(int index) override; private: static int constexpr kInvalidIndex = -1; @@ -54,6 +55,7 @@ private: void InitiateForegroundSearch(size_t index); void OnUpdate(View::ResultType type, size_t sampleIndex, Edits::Update const & update); + void OnSampleUpdate(size_t sampleIndex); void UpdateViewOnResults(search::Results const & results); void ShowMarks(Context const & context); diff --git a/search/search_quality/assessment_tool/main_view.cpp b/search/search_quality/assessment_tool/main_view.cpp index a2b10c393c..838693d8cc 100644 --- a/search/search_quality/assessment_tool/main_view.cpp +++ b/search/search_quality/assessment_tool/main_view.cpp @@ -110,7 +110,8 @@ void MainView::OnSearchCompleted() } void MainView::ShowSample(size_t sampleIndex, search::Sample const & sample, - boost::optional const & position, bool hasEdits) + boost::optional const & position, bool isUseless, + bool hasEdits) { m_sampleLocale = sample.m_locale; @@ -121,7 +122,7 @@ void MainView::ShowSample(size_t sampleIndex, search::Sample const & sample, OnResultChanged(sampleIndex, ResultType::Found, Edits::Update::MakeAll()); OnResultChanged(sampleIndex, ResultType::NonFound, Edits::Update::MakeAll()); - OnSampleChanged(sampleIndex, hasEdits); + OnSampleChanged(sampleIndex, isUseless, hasEdits); } void MainView::AddFoundResults(search::Results::ConstIter begin, search::Results::ConstIter end) @@ -190,12 +191,13 @@ void MainView::OnResultChanged(size_t sampleIndex, ResultType type, Edits::Updat } } -void MainView::OnSampleChanged(size_t sampleIndex, bool hasEdits) +void MainView::OnSampleChanged(size_t sampleIndex, bool isUseless, bool hasEdits) { m_samplesView->OnUpdate(sampleIndex); if (!m_samplesView->IsSelected(sampleIndex)) return; - SetSampleDockTitle(hasEdits); + SetSampleDockTitle(isUseless, hasEdits); + m_sampleView->OnUselessnessChanged(isUseless); } void MainView::OnSamplesChanged(bool hasEdits) @@ -225,7 +227,7 @@ void MainView::Clear() SetSamplesDockTitle(false /* hasEdits */); m_sampleView->Clear(); - SetSampleDockTitle(false /* hasEdits */); + SetSampleDockTitle(false /* isUseless */, false /* hasEdits */); m_skipFeatureInfoDialog = false; m_sampleLocale.clear(); @@ -361,6 +363,9 @@ void MainView::InitDocks() connect(model, &QItemSelectionModel::selectionChanged, this, &MainView::OnSampleSelected); } + connect(m_samplesView, &SamplesView::FlipSampleUsefulness, + [this](int index) { m_model->FlipSampleUsefulness(index); }); + m_samplesDock = CreateDock(*m_samplesView); addDockWidget(Qt::LeftDockWidgetArea, m_samplesDock); SetSamplesDockTitle(false /* hasEdits */); @@ -393,7 +398,7 @@ void MainView::InitDocks() connect(m_sampleDock, &QDockWidget::dockLocationChanged, [this](Qt::DockWidgetArea area) { m_sampleView->OnLocationChanged(area); }); addDockWidget(Qt::RightDockWidgetArea, m_sampleDock); - SetSampleDockTitle(false /* hasEdits */); + SetSampleDockTitle(false /* isUseless */, false /* hasEdits */); } void MainView::Open() @@ -479,13 +484,15 @@ void MainView::SetSamplesDockTitle(bool hasEdits) m_samplesDock->setWindowTitle(tr("Samples")); } -void MainView::SetSampleDockTitle(bool hasEdits) +void MainView::SetSampleDockTitle(bool isUseless, bool hasEdits) { CHECK(m_sampleDock, ()); + std::string title = "Sample"; if (hasEdits) - m_sampleDock->setWindowTitle(tr("Sample *")); - else - m_sampleDock->setWindowTitle(tr("Sample")); + title += " *"; + if (isUseless) + title += " (useless)"; + m_sampleDock->setWindowTitle(tr(title.data())); } MainView::SaveResult MainView::TryToSaveEdits(QString const & msg) diff --git a/search/search_quality/assessment_tool/main_view.hpp b/search/search_quality/assessment_tool/main_view.hpp index 6ec9491162..84d4da55da 100644 --- a/search/search_quality/assessment_tool/main_view.hpp +++ b/search/search_quality/assessment_tool/main_view.hpp @@ -33,7 +33,8 @@ public: void OnSearchStarted() override; void OnSearchCompleted() override; void ShowSample(size_t sampleIndex, search::Sample const & sample, - boost::optional const & position, bool hasEdits) override; + boost::optional const & position, bool isUseless, + bool hasEdits) override; void AddFoundResults(search::Results::ConstIter begin, search::Results::ConstIter end) override; void ShowNonFoundResults(std::vector const & results, @@ -53,7 +54,7 @@ public: void OnResultChanged(size_t sampleIndex, ResultType type, Edits::Update const & update) override; void SetEdits(size_t sampleIndex, Edits & foundResultsEdits, Edits & nonFoundResultsEdits) override; - void OnSampleChanged(size_t sampleIndex, bool hasEdits) override; + void OnSampleChanged(size_t sampleIndex, bool isUseless, bool hasEdits) override; void OnSamplesChanged(bool hasEdits) override; void ShowError(std::string const & msg) override; @@ -105,7 +106,7 @@ private: void InitiateBackgroundSearch(); void SetSamplesDockTitle(bool hasEdits); - void SetSampleDockTitle(bool hasEdits); + void SetSampleDockTitle(bool isUseless, bool hasEdits); SaveResult TryToSaveEdits(QString const & msg); void AddSelectedFeature(QPoint const & p); diff --git a/search/search_quality/assessment_tool/model.hpp b/search/search_quality/assessment_tool/model.hpp index 3b98b75dd0..fe9b3ea98d 100644 --- a/search/search_quality/assessment_tool/model.hpp +++ b/search/search_quality/assessment_tool/model.hpp @@ -35,6 +35,7 @@ public: virtual bool AlreadyInSamples(FeatureID const & id) = 0; virtual void AddNonFoundResult(FeatureID const & id) = 0; + virtual void FlipSampleUsefulness(int index) = 0; protected: View * m_view = nullptr; diff --git a/search/search_quality/assessment_tool/sample_view.cpp b/search/search_quality/assessment_tool/sample_view.cpp index 969de74900..cd702c7230 100644 --- a/search/search_quality/assessment_tool/sample_view.cpp +++ b/search/search_quality/assessment_tool/sample_view.cpp @@ -101,6 +101,7 @@ SampleView::SampleView(QWidget * parent, Framework & framework) auto * layout = BuildSubLayout(*mainLayout, *this /* parent */, &m_relatedQueriesBox); SetVerticalStretch(*m_relatedQueriesBox, 1 /* stretch */); + layout->addWidget(new QLabel(tr("Related queries"))); m_relatedQueries = new QListWidget(); @@ -121,6 +122,13 @@ SampleView::SampleView(QWidget * parent, Framework & framework) layout->addWidget(m_markAllAsIrrelevant); } + { + m_uselessnessLabel = new QLabel(this /* parent */); + m_uselessnessLabel->setText(tr("Sample is marked as useless")); + m_uselessnessLabel->hide(); + mainLayout->addWidget(m_uselessnessLabel); + } + { auto * layout = BuildSubLayout(*mainLayout, *this /* parent */, &m_foundResultsBox); @@ -305,6 +313,25 @@ void SampleView::SetEdits(Edits & resultsEdits, Edits & nonFoundResultsEdits) m_nonFoundResultsEdits = &nonFoundResultsEdits; } +void SampleView::OnUselessnessChanged(bool isUseless) +{ + if (isUseless) + { + m_uselessnessLabel->show(); + m_foundResultsBox->hide(); + m_nonFoundResultsBox->hide(); + m_markAllAsRelevant->hide(); + m_markAllAsIrrelevant->hide(); + } + else + { + m_uselessnessLabel->hide(); + m_foundResultsBox->show(); + m_markAllAsRelevant->show(); + m_markAllAsIrrelevant->show(); + } +} + void SampleView::Clear() { m_query->hide(); diff --git a/search/search_quality/assessment_tool/sample_view.hpp b/search/search_quality/assessment_tool/sample_view.hpp index 6fc6c9eba7..c8e3578c00 100644 --- a/search/search_quality/assessment_tool/sample_view.hpp +++ b/search/search_quality/assessment_tool/sample_view.hpp @@ -44,6 +44,8 @@ public: void SetEdits(Edits & resultsEdits, Edits & nonFoundResultsEdits); + void OnUselessnessChanged(bool isUseless); + void Clear(); ResultsView & GetFoundResultsView() { return *m_foundResults; } @@ -81,6 +83,8 @@ private: QPushButton * m_markAllAsRelevant = nullptr; QPushButton * m_markAllAsIrrelevant = nullptr; + QLabel * m_uselessnessLabel = nullptr; + ResultsView * m_foundResults = nullptr; QWidget * m_foundResultsBox = nullptr; diff --git a/search/search_quality/assessment_tool/samples_view.cpp b/search/search_quality/assessment_tool/samples_view.cpp index 26d8719f37..79376b2226 100644 --- a/search/search_quality/assessment_tool/samples_view.cpp +++ b/search/search_quality/assessment_tool/samples_view.cpp @@ -3,9 +3,15 @@ #include "search/search_quality/assessment_tool/helpers.hpp" #include "base/assert.hpp" +#include "base/logging.hpp" +#include + +#include #include +#include #include +#include // SamplesView::Model ------------------------------------------------------------------------------ SamplesView::Model::Model(QWidget * parent) @@ -55,3 +61,19 @@ bool SamplesView::IsSelected(size_t index) const { return selectionModel()->isRowSelected(base::checked_cast(index), QModelIndex()); } + +void SamplesView::contextMenuEvent(QContextMenuEvent * event) +{ + QModelIndex modelIndex = selectionModel()->currentIndex(); + if (!modelIndex.isValid()) + return; + + int const index = modelIndex.row(); + bool const isUseless = m_model->SampleIsUseless(index); + + QMenu menu(this); + auto const text = std::string(isUseless ? "unmark" : "mark") + " sample as useless"; + auto const * action = menu.addAction(text.c_str()); + connect(action, &QAction::triggered, [this, index]() { emit FlipSampleUsefulness(index); }); + menu.exec(event->globalPos()); +} diff --git a/search/search_quality/assessment_tool/samples_view.hpp b/search/search_quality/assessment_tool/samples_view.hpp index f656234619..b8dcea1ccf 100644 --- a/search/search_quality/assessment_tool/samples_view.hpp +++ b/search/search_quality/assessment_tool/samples_view.hpp @@ -8,10 +8,13 @@ #include #include +#include #include class SamplesView : public QTableView { + Q_OBJECT + public: explicit SamplesView(QWidget * parent); @@ -20,6 +23,12 @@ public: void OnUpdate(size_t index) { m_model->OnUpdate(index); } void Clear() { m_model->SetSamples(ContextList::SamplesSlice{}); } + // QMainWindow overrides: + void contextMenuEvent(QContextMenuEvent * event) override; + +signals: + void FlipSampleUsefulness(int index); + private: class Model : public QStandardItemModel { @@ -42,6 +51,8 @@ private: emit dataChanged(ix, ix); } + bool SampleIsUseless(int index) const { return m_samples.IsUseless(index); } + // QStandardItemModel overrides: QVariant data(QModelIndex const & index, int role = Qt::DisplayRole) const override; diff --git a/search/search_quality/assessment_tool/view.hpp b/search/search_quality/assessment_tool/view.hpp index 6abf635ca3..153c3cb00d 100644 --- a/search/search_quality/assessment_tool/view.hpp +++ b/search/search_quality/assessment_tool/view.hpp @@ -34,7 +34,8 @@ public: virtual void OnSearchStarted() = 0; virtual void OnSearchCompleted() = 0; virtual void ShowSample(size_t index, search::Sample const & sample, - boost::optional const & position, bool hasEdits) = 0; + boost::optional const & position, bool isUseless, + bool hasEdits) = 0; virtual void AddFoundResults(search::Results::ConstIter begin, search::Results::ConstIter end) = 0; @@ -55,7 +56,7 @@ public: virtual void OnResultChanged(size_t sampleIndex, ResultType type, Edits::Update const & update) = 0; virtual void SetEdits(size_t index, Edits & foundResultsEdits, Edits & nonFoundResultsEdits) = 0; - virtual void OnSampleChanged(size_t sampleIndex, bool hasEdits) = 0; + virtual void OnSampleChanged(size_t sampleIndex, bool isUseless, bool hasEdits) = 0; virtual void OnSamplesChanged(bool hasEdits) = 0; virtual void ShowError(std::string const & msg) = 0; diff --git a/search/search_quality/sample.cpp b/search/search_quality/sample.cpp index 18ca3c5880..59299d839f 100644 --- a/search/search_quality/sample.cpp +++ b/search/search_quality/sample.cpp @@ -168,6 +168,7 @@ void Sample::DeserializeFromJSONImpl(json_t * root) FromJSONObject(root, "viewport", m_viewport); FromJSONObjectOptional(root, "results", m_results); FromJSONObjectOptional(root, "related_queries", m_relatedQueries); + FromJSONObjectOptionalField(root, "useless", m_useless); } void Sample::SerializeToJSONImpl(json_t & root) const @@ -178,6 +179,8 @@ void Sample::SerializeToJSONImpl(json_t & root) const ToJSONObject(root, "viewport", m_viewport); ToJSONObject(root, "results", m_results); ToJSONObject(root, "related_queries", m_relatedQueries); + if (m_useless) + ToJSONObject(root, "useless", m_useless); } void Sample::FillSearchParams(search::SearchParams & params) const diff --git a/search/search_quality/sample.hpp b/search/search_quality/sample.hpp index e787ccab1e..572ff87ac1 100644 --- a/search/search_quality/sample.hpp +++ b/search/search_quality/sample.hpp @@ -76,6 +76,11 @@ struct Sample m2::RectD m_viewport = m2::RectD(0, 0, 0, 0); std::vector m_results; std::vector m_relatedQueries; + + // A useless sample is usually a result of the user exploring + // the search engine without a clear search intent or a sample + // that cannot be assessed properly using only the data it contains. + bool m_useless = false; }; void FromJSONObject(json_t * root, char const * field,