Copyright 2020, 2021, 2022 Red Hat, 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.
|
package storage
import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/rs/zerolog/log"
"github.com/RedHatInsights/insights-results-aggregator/metrics"
"github.com/RedHatInsights/insights-results-aggregator/types"
)
|
UserFeedbackOnRule shows user's feedback on rule
|
type UserFeedbackOnRule struct {
ClusterID types . ClusterName
RuleID types . RuleID
ErrorKey types . ErrorKey
UserID types . UserID
Message string
UserVote types . UserVote
AddedAt time . Time
UpdatedAt time . Time
}
|
VoteOnRule likes or dislikes rule for cluster by user. If entry exists, it overwrites it
|
func ( storage OCPRecommendationsDBStorage ) VoteOnRule (
clusterID types . ClusterName ,
ruleID types . RuleID ,
errorKey types . ErrorKey ,
orgID types . OrgID ,
userID types . UserID ,
userVote types . UserVote ,
voteMessage string ,
) error {
return storage . addOrUpdateUserFeedbackOnRuleForCluster ( clusterID , ruleID , errorKey , orgID , userID , & userVote , & voteMessage )
}
|
AddOrUpdateFeedbackOnRule adds feedback on rule for cluster by user. If entry exists, it overwrites it
|
func ( storage OCPRecommendationsDBStorage ) AddOrUpdateFeedbackOnRule (
clusterID types . ClusterName ,
ruleID types . RuleID ,
errorKey types . ErrorKey ,
orgID types . OrgID ,
userID types . UserID ,
message string ,
) error {
return storage . addOrUpdateUserFeedbackOnRuleForCluster ( clusterID , ruleID , errorKey , orgID , userID , nil , & message )
}
|
addOrUpdateUserFeedbackOnRuleForCluster adds or updates feedback
will update user vote and messagePtr if the pointers are not nil
|
func ( storage OCPRecommendationsDBStorage ) addOrUpdateUserFeedbackOnRuleForCluster (
clusterID types . ClusterName ,
ruleID types . RuleID ,
errorKey types . ErrorKey ,
orgID types . OrgID ,
userID types . UserID ,
userVotePtr * types . UserVote ,
messagePtr * string ,
) error {
updateVote := false
updateMessage := false
userVote := types . UserVoteNone
message := ""
if userVotePtr != nil {
updateVote = true
userVote = * userVotePtr
}
if messagePtr != nil {
updateMessage = true
message = * messagePtr
}
query , err := storage . constructUpsertClusterRuleUserFeedback ( updateVote , updateMessage )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "Unable to create upsert statement" )
return err
}
statement , err := storage . connection . Prepare ( query )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "Unable to prepare statement" )
return err
}
defer func ( ) {
err := statement . Close ( )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( closeStatementError )
}
} ( )
now := time . Now ( )
_ , err = statement . Exec ( clusterID , ruleID , userID , userVote , now , now , message , errorKey , orgID )
err = types . ConvertDBError ( err , nil )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "addOrUpdateUserFeedbackOnRuleForCluster" )
return err
}
metrics . FeedbackOnRules . Inc ( )
return nil
}
func ( storage OCPRecommendationsDBStorage ) constructUpsertClusterRuleUserFeedback ( updateVote , updateMessage bool ) ( string , error ) {
var query string
switch storage . dbDriverType {
case types . DBDriverPostgres , types . DBDriverGeneral :
query = `
INSERT INTO cluster_rule_user_feedback
(cluster_id, rule_id, user_id, user_vote, added_at, updated_at, message, error_key, org_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
`
var updates [ ] string
if updateVote {
updates = append ( updates , "user_vote = $4" )
}
if updateMessage {
updates = append ( updates , "message = $7" )
}
if len ( updates ) > 0 {
updates = append ( updates , "updated_at = $6" )
query += "ON CONFLICT (cluster_id, rule_id, error_key, user_id) DO UPDATE SET "
query += strings . Join ( updates , ", " )
}
default :
return "" , fmt . Errorf ( "DB driver %v is not supported" , storage . dbDriverType )
}
return query , nil
}
|
GetUserFeedbackOnRule gets user feedback from DB
|
func ( storage OCPRecommendationsDBStorage ) GetUserFeedbackOnRule (
clusterID types . ClusterName , ruleID types . RuleID , errorKey types . ErrorKey , userID types . UserID ,
) ( * UserFeedbackOnRule , error ) {
feedback := UserFeedbackOnRule { }
err := storage . connection . QueryRow (
`SELECT cluster_id, rule_id, error_key, user_id, message, user_vote, added_at, updated_at
FROM cluster_rule_user_feedback
WHERE cluster_id = $1 AND rule_id = $2 AND error_key = $3 AND user_id = $4` ,
clusterID , ruleID , errorKey , userID ,
) . Scan (
& feedback . ClusterID ,
& feedback . RuleID ,
& feedback . ErrorKey ,
& feedback . UserID ,
& feedback . Message ,
& feedback . UserVote ,
& feedback . AddedAt ,
& feedback . UpdatedAt ,
)
switch {
case err == sql . ErrNoRows :
return nil , & types . ItemNotFoundError {
ItemID : fmt . Sprintf ( "%v/%v/%v" , clusterID , ruleID , userID ) ,
}
case err != nil :
return nil , err
}
return & feedback , nil
}
|
GetUserFeedbackOnRuleDisable gets user feedback from DB
|
func ( storage OCPRecommendationsDBStorage ) GetUserFeedbackOnRuleDisable (
clusterID types . ClusterName , ruleID types . RuleID , errorKey types . ErrorKey , userID types . UserID ,
) ( * UserFeedbackOnRule , error ) {
feedback := UserFeedbackOnRule { }
err := storage . connection . QueryRow (
`SELECT cluster_id, rule_id, error_key, user_id, message, added_at, updated_at
FROM cluster_user_rule_disable_feedback
WHERE cluster_id = $1 AND rule_id = $2 AND error_key = $3 AND user_id = $4` ,
clusterID , ruleID , errorKey , userID ,
) . Scan (
& feedback . ClusterID ,
& feedback . RuleID ,
& feedback . ErrorKey ,
& feedback . UserID ,
& feedback . Message ,
& feedback . AddedAt ,
& feedback . UpdatedAt ,
)
switch {
case err == sql . ErrNoRows :
return nil , & types . ItemNotFoundError {
ItemID : fmt . Sprintf ( "%v/%v/%v" , clusterID , userID , ruleID ) ,
}
case err != nil :
return nil , err
}
return & feedback , nil
}
|
GetUserFeedbackOnRules gets user feedbacks for defined array of rule IDs from DB
|
func ( storage OCPRecommendationsDBStorage ) GetUserFeedbackOnRules (
clusterID types . ClusterName , rulesReport [ ] types . RuleOnReport , userID types . UserID ,
) ( map [ types . RuleID ] types . UserVote , error ) {
ruleIDs := make ( [ ] string , 0 )
for _ , v := range rulesReport {
ruleIDs = append ( ruleIDs , string ( v . Module ) )
}
feedbacks := make ( map [ types . RuleID ] types . UserVote )
query := `SELECT rule_id, user_vote
FROM cluster_rule_user_feedback
WHERE cluster_id = $1 AND rule_id in (%v) AND user_id = $2`
whereInStatement := inClauseFromSlice ( ruleIDs )
query = fmt . Sprintf ( query , whereInStatement )
rows , err := storage . connection . Query ( query , clusterID , userID )
if err != nil {
return feedbacks , err
}
defer closeRows ( rows )
for rows . Next ( ) {
var (
ruleID types . RuleID
userVote types . UserVote
)
err = rows . Scan (
& ruleID ,
& userVote ,
)
if err == nil {
feedbacks [ ruleID ] = userVote
} else {
log . Error ( ) . Err ( err ) . Msg ( "GetUserFeedbackOnRules" )
return nil , err
}
}
return feedbacks , nil
}
|
GetUserDisableFeedbackOnRules gets user disable feedbacks for defined array of rule IDs from DB
|
func ( storage OCPRecommendationsDBStorage ) GetUserDisableFeedbackOnRules (
clusterID types . ClusterName , rulesReport [ ] types . RuleOnReport , userID types . UserID ,
) ( map [ types . RuleID ] UserFeedbackOnRule , error ) {
feedbacks := make ( map [ types . RuleID ] UserFeedbackOnRule )
for _ , rule := range rulesReport {
feedback , err := storage . GetUserFeedbackOnRuleDisable ( clusterID , rule . Module , rule . ErrorKey , userID )
if err != nil {
if _ , itemNotFound := err . ( * types . ItemNotFoundError ) ; ! itemNotFound {
return nil , err
}
} else {
|
since rules always hit only 1 error key, it's enough to select via module
|
feedbacks [ rule . Module ] = * feedback
}
}
return feedbacks , nil
}
|
AddFeedbackOnRuleDisable adds feedback on rule disable
|
func ( storage OCPRecommendationsDBStorage ) AddFeedbackOnRuleDisable (
clusterID types . ClusterName ,
ruleID types . RuleID ,
errorKey types . ErrorKey ,
orgID types . OrgID ,
userID types . UserID ,
message string ,
) error {
statement , err := storage . connection . Prepare ( `
INSERT INTO cluster_user_rule_disable_feedback
(cluster_id, org_id, user_id, rule_id, error_key, message, added_at, updated_at)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT
(cluster_id, user_id, rule_id, error_key)
DO UPDATE SET updated_at = $8, message = $6;
` )
if err != nil {
return err
}
defer func ( ) {
err := statement . Close ( )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( closeStatementError )
}
} ( )
now := time . Now ( )
_ , err = statement . Exec ( clusterID , orgID , userID , ruleID , errorKey , message , now , now )
err = types . ConvertDBError ( err , nil )
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "addOrUpdateUserFeedbackOnRuleDisableForCluster" )
return err
}
metrics . FeedbackOnRules . Inc ( )
return nil
}
|